fix(javascript): preserve alias and container uncertainty
This commit is contained in:
parent
99f257bbf0
commit
73e98b2516
|
|
@ -838,6 +838,85 @@ it("retains array length presence without inventing an exact length", () => {
|
|||
).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["object spread", 'const root = { known: "value", ...dynamic };'],
|
||||
["array spread", 'const root = ["value", ...dynamic];'],
|
||||
])(
|
||||
"keeps omitted initializer coverage on the owning binding: %s",
|
||||
(_label, source) => {
|
||||
const graph = graphFor(source);
|
||||
const root = graph.nodes.find(
|
||||
({ kind, label }) => kind === "binding" && label === "root",
|
||||
);
|
||||
if (root === undefined) throw new Error("Expected root binding");
|
||||
const result = queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "semantic-node", node_id: root.node_id },
|
||||
direction: "forward-influence",
|
||||
});
|
||||
|
||||
expect(result.coverage.status).toBe("partial");
|
||||
expect(result.unknowns).toContainEqual(
|
||||
expect.objectContaining({
|
||||
node_id: root.node_id,
|
||||
family: "object-flow",
|
||||
reason: "ambiguous-target",
|
||||
detail: expect.stringContaining("Initializer container has unknown"),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["bare if", 'if (flag) alias = { mode: "other" }; alias.mode = "updated";'],
|
||||
[
|
||||
"logical expression",
|
||||
'flag && (alias = { mode: "other" }); alias.mode = "updated";',
|
||||
],
|
||||
[
|
||||
"logical assignment",
|
||||
'flag ||= (alias = { mode: "other" }); alias.mode = "updated";',
|
||||
],
|
||||
["or assignment", 'alias ||= { mode: "other" }; alias.mode = "updated";'],
|
||||
[
|
||||
"nullish assignment",
|
||||
'alias ??= { mode: "other" }; alias.mode = "updated";',
|
||||
],
|
||||
[
|
||||
"conditional mutation",
|
||||
'alias = { mode: "other" }; if (flag) alias.mode = "updated";',
|
||||
],
|
||||
[
|
||||
"short-circuit mutation",
|
||||
'alias = { mode: "other" }; flag && (alias.mode = "updated");',
|
||||
],
|
||||
])(
|
||||
"preserves possible alias mutation uncertainty in queries: %s",
|
||||
(_label, body) => {
|
||||
const graph = graphFor(`
|
||||
const shared = { mode: "initial" };
|
||||
let alias = shared;
|
||||
${body}
|
||||
`);
|
||||
const sharedMode = graph.nodes.find(
|
||||
({ kind, identity, properties }) =>
|
||||
kind === "property-slot" &&
|
||||
properties.property_pointer === "/mode" &&
|
||||
identity.role_key.includes("binding:shared"),
|
||||
);
|
||||
if (sharedMode === undefined) throw new Error("Expected shared mode slot");
|
||||
const result = queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "semantic-node", node_id: sharedMode.node_id },
|
||||
direction: "backward-provenance",
|
||||
});
|
||||
|
||||
expect(sharedMode.properties.presence).toBe("unknown-coverage");
|
||||
expect(result.nodes).toContainEqual(
|
||||
expect.objectContaining({ node_id: sharedMode.node_id }),
|
||||
);
|
||||
expect(result.coverage.status).toBe("partial");
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["{}", "unknownRoot", "{ a: null }"])(
|
||||
"preserves the requested deep leaf name after a blocked prefix: %s",
|
||||
(initializer) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ApplicationGraphEvidence } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
|
||||
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ export const unavailableSemanticRootEvidence = (
|
|||
/** Explicit unknown evidence at one unresolved dynamic syntax location. */
|
||||
export const unknownSemanticEvidence = (
|
||||
file: JavaScriptArtifactFile,
|
||||
location: JavaScriptSourceRange,
|
||||
location: JavaScriptSourceRange | null,
|
||||
): ApplicationGraphEvidence => ({
|
||||
...observedSemanticEvidence(file, location),
|
||||
authority: "unknown",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ import {
|
|||
semanticPropertyPointer,
|
||||
type JavaScriptSemanticSlot,
|
||||
} from "../../domain/javascript/javascriptSemanticSlots.js";
|
||||
import type {
|
||||
JavaScriptSemanticBinding,
|
||||
JavaScriptSemanticValue,
|
||||
} from "../../domain/javascript/javascriptSemanticIr.js";
|
||||
import type { JavaScriptSemanticBinding } from "../../domain/javascript/javascriptSemanticIr.js";
|
||||
import type { JavaScriptSemanticValue } from "../../domain/javascript/javascriptSemanticValueTypes.js";
|
||||
import { createJavaScriptSemanticGraphUnknown } from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
import {
|
||||
retainSemanticGraphNode,
|
||||
|
|
@ -14,7 +12,10 @@ import {
|
|||
addSemanticGraphUnknown,
|
||||
} from "./JavaScriptSemanticGraphConstruction.js";
|
||||
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
|
||||
import { observedSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
||||
import {
|
||||
observedSemanticEvidence,
|
||||
unknownSemanticEvidence,
|
||||
} from "./JavaScriptSemanticGraphEvidence.js";
|
||||
|
||||
/** Project bounded literal values and object slots for exact query seeds. */
|
||||
export const projectSemanticValues = (
|
||||
|
|
@ -90,6 +91,24 @@ const projectValue = (input: ValueProjectionInput): void => {
|
|||
path: propertyPath,
|
||||
});
|
||||
}
|
||||
if (container.coverage.status === "partial")
|
||||
addSemanticGraphUnknown(
|
||||
context.state,
|
||||
createJavaScriptSemanticGraphUnknown({
|
||||
node_id: target.node_id,
|
||||
family: "object-flow",
|
||||
relation_kinds: ["writes-property"],
|
||||
reason: "ambiguous-target",
|
||||
detail: `Initializer container has unknown ${
|
||||
value.status === "object" ? "properties" : "items"
|
||||
}${container.coverage.omitted === null ? " with an unknown omitted count" : `; ${container.coverage.omitted} omitted`}.`,
|
||||
candidate_node_ids: [target.node_id],
|
||||
evidence: unknownSemanticEvidence(
|
||||
context.file,
|
||||
binding.definitions[0]?.location ?? null,
|
||||
),
|
||||
}),
|
||||
);
|
||||
} else if (value.status === "unknown" && value.resourceLimit !== undefined) {
|
||||
const location = binding.definitions[0]?.location ?? null;
|
||||
const evidence = observedSemanticEvidence(context.file, location);
|
||||
|
|
|
|||
|
|
@ -209,6 +209,17 @@ describe("JavaScript semantic analysis: read-modify-write", () => {
|
|||
"read",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not model a compound binding write as a competing replacement", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
let count = 1;
|
||||
count += 2;
|
||||
`);
|
||||
|
||||
expect(topLevelBinding(ir, "count").value).toMatchObject({
|
||||
status: "unknown",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: dataflow 2", () => {
|
||||
|
|
@ -448,21 +459,36 @@ describe("nonfinite static values", () => {
|
|||
|
||||
describe("binding write invalidation", () => {
|
||||
it.each([
|
||||
"let value = 1; value++; const observed = value;",
|
||||
"let value = 1; --value; const observed = value;",
|
||||
'let value = "old"; [value] = ["new"]; const observed = value;',
|
||||
'let value = "old"; ({key: value} = {key: "new"}); const observed = value;',
|
||||
'let value = "old"; for (value of ["new"]) {} const observed = value;',
|
||||
'let value = "old"; for (value in {new: true}) {} const observed = value;',
|
||||
])("does not retain an exact initializer across %s", (source) => {
|
||||
const ir = analyzeJavaScriptSemantics(source);
|
||||
expect(topLevelBinding(ir, "observed").value.status).toBe("ambiguous");
|
||||
expect(
|
||||
topLevelBinding(ir, "value").definitions.some(
|
||||
({ kind }) => kind === "assignment",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
["let value = 1; value++; const observed = value;", "unknown"],
|
||||
["let value = 1; --value; const observed = value;", "unknown"],
|
||||
[
|
||||
'let value = "old"; [value] = ["new"]; const observed = value;',
|
||||
"ambiguous",
|
||||
],
|
||||
[
|
||||
'let value = "old"; ({key: value} = {key: "new"}); const observed = value;',
|
||||
"ambiguous",
|
||||
],
|
||||
[
|
||||
'let value = "old"; for (value of ["new"]) {} const observed = value;',
|
||||
"ambiguous",
|
||||
],
|
||||
[
|
||||
'let value = "old"; for (value in {new: true}) {} const observed = value;',
|
||||
"ambiguous",
|
||||
],
|
||||
] as const)(
|
||||
"does not retain an exact initializer across %s",
|
||||
(source, status) => {
|
||||
const ir = analyzeJavaScriptSemantics(source);
|
||||
expect(topLevelBinding(ir, "observed").value.status).toBe(status);
|
||||
expect(
|
||||
topLevelBinding(ir, "value").definitions.some(
|
||||
({ kind }) => kind === "assignment",
|
||||
),
|
||||
).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not treat destructuring keys or member owners as assigned bindings", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
|
|
|
|||
|
|
@ -76,6 +76,85 @@ describe("JavaScript semantic values after explicit property mutations", () => {
|
|||
).toEqual({ status: "literal", value: "initial" });
|
||||
});
|
||||
|
||||
it("does not propagate writes through an alias after an unconditional rebind", () => {
|
||||
expect(
|
||||
resultValue(`
|
||||
const shared = { mode: "initial" };
|
||||
let alias = shared;
|
||||
alias = { mode: "other" };
|
||||
alias.mode = "updated";
|
||||
return shared.mode;
|
||||
`),
|
||||
).toEqual({ status: "literal", value: "initial" });
|
||||
});
|
||||
|
||||
it.each([
|
||||
'if (flag) alias = { mode: "other" }; alias.mode = "updated";',
|
||||
'flag && (alias = { mode: "other" }); alias.mode = "updated";',
|
||||
'flag ||= (alias = { mode: "other" }); alias.mode = "updated";',
|
||||
'alias ||= { mode: "other" }; alias.mode = "updated";',
|
||||
'alias ??= { mode: "other" }; alias.mode = "updated";',
|
||||
])(
|
||||
"keeps possible source aliases unknown across conditional rebinds",
|
||||
(body) => {
|
||||
expect(
|
||||
resultValue(`
|
||||
const shared = { mode: "initial" };
|
||||
let alias = shared;
|
||||
${body}
|
||||
return shared.mode;
|
||||
`)?.status,
|
||||
).toBe("unknown");
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps mutation under a bare conditional unknown across a rebind", () => {
|
||||
expect(
|
||||
resultValue(`
|
||||
const shared = { mode: "initial" };
|
||||
let alias = shared;
|
||||
alias = { mode: "other" };
|
||||
if (flag) alias.mode = "updated";
|
||||
return shared.mode;
|
||||
`)?.status,
|
||||
).toBe("unknown");
|
||||
});
|
||||
|
||||
it("does not treat a short-circuit assignment RHS as an unconditional value", () => {
|
||||
expect(
|
||||
resultValue(`
|
||||
let value;
|
||||
value &&= 2;
|
||||
return value;
|
||||
`)?.status,
|
||||
).toBe("unknown");
|
||||
});
|
||||
|
||||
it("keeps possible alias mutations conservative across short-circuit assignment", () => {
|
||||
expect(
|
||||
resultValue(`
|
||||
const shared = { mode: "initial" };
|
||||
let alias = shared;
|
||||
alias &&= { mode: "other" };
|
||||
alias.mode = "updated";
|
||||
return shared.mode;
|
||||
`)?.status,
|
||||
).toBe("unknown");
|
||||
});
|
||||
|
||||
it("propagates short-circuit assignment mutations to the RHS alias candidate", () => {
|
||||
expect(
|
||||
resultValue(`
|
||||
const shared = { mode: "initial" };
|
||||
const other = { mode: "other" };
|
||||
let alias = shared;
|
||||
alias &&= other;
|
||||
alias.mode = "updated";
|
||||
return other.mode;
|
||||
`)?.status,
|
||||
).toBe("unknown");
|
||||
});
|
||||
|
||||
it("retains ordinary immutable object and array projection", () => {
|
||||
expect(
|
||||
resultValue(
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import {
|
|||
collectSemanticReferences,
|
||||
immutableSemanticBindings,
|
||||
immutableSemanticScopes,
|
||||
semanticStaticPropertyKey,
|
||||
} from "./javascriptSemanticProjection.js";
|
||||
import { semanticStaticPropertyKey } from "./javascriptAstValues.js";
|
||||
import type {
|
||||
JavaScriptSemanticAnalysisState,
|
||||
JavaScriptSemanticBindingState,
|
||||
|
|
@ -35,7 +35,8 @@ import {
|
|||
resolveSemanticModuleCallables,
|
||||
} from "./javascriptSemanticReturns.js";
|
||||
import { collectJavaScriptDerivedSemantics } from "./javascriptSemanticDerivedAnalysis.js";
|
||||
import { propertyName, range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import { propertyName } from "./javascriptAstValues.js";
|
||||
import { semanticCoverage } from "./javascriptSemanticCoverage.js";
|
||||
import { semanticResourceLimitsIn } from "./javascriptSemanticResourceLimits.js";
|
||||
import {
|
||||
|
|
@ -409,7 +410,16 @@ const bindInnerDeclaration = (
|
|||
});
|
||||
else if (t.isAssignmentExpression(node)) {
|
||||
if (t.isIdentifier(node.left))
|
||||
addAssignment(node.left, node.right, scope, state);
|
||||
addAssignment(
|
||||
node.left,
|
||||
node.operator === "=" ||
|
||||
node.operator === "||=" ||
|
||||
node.operator === "??="
|
||||
? node.right
|
||||
: node,
|
||||
scope,
|
||||
state,
|
||||
);
|
||||
else
|
||||
for (const identifier of assignedPatternIdentifiers(node.left))
|
||||
addAssignment(identifier, node, scope, state);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as t from "@babel/types";
|
|||
import {
|
||||
resolveSemanticBindingState,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
type JavaScriptSemanticBindingState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
import { evaluateSemanticBinding } from "./javascriptSemanticValues.js";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
|
|
@ -15,10 +16,17 @@ export const collectSemanticMemberMutations = (
|
|||
program: t.Program,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): void => {
|
||||
const parents = new WeakMap<t.Node, t.Node>();
|
||||
traverseJavaScriptAst(program, {
|
||||
enter: (node, parent) => {
|
||||
if (parent !== null) parents.set(node, parent);
|
||||
},
|
||||
});
|
||||
const markValue = (
|
||||
node: t.Node,
|
||||
path: PropertyPath,
|
||||
bindings: ReadonlySet<string>,
|
||||
mutation: t.Node,
|
||||
): void => {
|
||||
if (t.isIdentifier(node)) {
|
||||
const binding = resolveSemanticBindingState(state, node, node.name);
|
||||
|
|
@ -27,11 +35,16 @@ export const collectSemanticMemberMutations = (
|
|||
if (value.status === "literal" || value.status === "union") return;
|
||||
binding.mutatedPaths.push(path);
|
||||
const nested = new Set([...bindings, binding.bindingId]);
|
||||
for (const initializer of binding.initializers)
|
||||
for (const initializer of mutationInitializers(
|
||||
binding,
|
||||
mutation,
|
||||
parents,
|
||||
))
|
||||
markValue(
|
||||
initializer.node,
|
||||
[...initializer.projection, ...path],
|
||||
nested,
|
||||
mutation,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -47,43 +60,130 @@ export const collectSemanticMemberMutations = (
|
|||
);
|
||||
current = current.object;
|
||||
}
|
||||
markValue(current, [...members.reverse(), ...path], bindings);
|
||||
markValue(current, [...members.reverse(), ...path], bindings, mutation);
|
||||
return;
|
||||
}
|
||||
for (const value of referencedValues(node, path))
|
||||
markValue(value.node, value.path, bindings);
|
||||
markValue(value.node, value.path, bindings, mutation);
|
||||
};
|
||||
const markTarget = (node: t.Node): void => {
|
||||
const markTarget = (node: t.Node, mutation: t.Node): void => {
|
||||
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node))
|
||||
markValue(
|
||||
node.object,
|
||||
[semanticStaticPropertyKey(node.property, node.computed)],
|
||||
new Set(),
|
||||
mutation,
|
||||
);
|
||||
else if (t.isRestElement(node)) markTarget(node.argument);
|
||||
else if (t.isAssignmentPattern(node)) markTarget(node.left);
|
||||
else if (t.isRestElement(node)) markTarget(node.argument, mutation);
|
||||
else if (t.isAssignmentPattern(node)) markTarget(node.left, mutation);
|
||||
else if (t.isArrayPattern(node)) {
|
||||
for (const element of node.elements)
|
||||
if (element !== null) markTarget(element);
|
||||
if (element !== null) markTarget(element, mutation);
|
||||
} else if (t.isObjectPattern(node)) {
|
||||
for (const property of node.properties)
|
||||
markTarget(
|
||||
t.isRestElement(property) ? property.argument : property.value,
|
||||
mutation,
|
||||
);
|
||||
}
|
||||
};
|
||||
traverseJavaScriptAst(program, {
|
||||
enter: (node) => {
|
||||
if (t.isAssignmentExpression(node)) markTarget(node.left);
|
||||
else if (t.isUpdateExpression(node)) markTarget(node.argument);
|
||||
if (t.isAssignmentExpression(node)) markTarget(node.left, node);
|
||||
else if (t.isUpdateExpression(node)) markTarget(node.argument, node);
|
||||
else if (t.isUnaryExpression(node, { operator: "delete" }))
|
||||
markTarget(node.argument);
|
||||
markTarget(node.argument, node);
|
||||
else if (t.isForOfStatement(node) || t.isForInStatement(node))
|
||||
markTarget(node.left);
|
||||
markTarget(node.left, node);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
interface StatementPosition {
|
||||
readonly body: readonly t.Statement[];
|
||||
readonly index: number;
|
||||
}
|
||||
|
||||
const statementPosition = (
|
||||
statement: t.Statement,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
): StatementPosition | null => {
|
||||
const parent = parents.get(statement);
|
||||
if (!t.isProgram(parent) && !t.isBlockStatement(parent)) return null;
|
||||
const index = parent.body.indexOf(statement);
|
||||
return index < 0 ? null : { body: parent.body, index };
|
||||
};
|
||||
|
||||
const directInitializerPosition = (
|
||||
node: t.Node,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
): StatementPosition | null => {
|
||||
const parent = parents.get(node);
|
||||
if (t.isVariableDeclarator(parent) && parent.init === node) {
|
||||
const declaration = parents.get(parent);
|
||||
return t.isVariableDeclaration(declaration)
|
||||
? statementPosition(declaration, parents)
|
||||
: null;
|
||||
}
|
||||
if (
|
||||
t.isAssignmentExpression(parent) &&
|
||||
parent.operator === "=" &&
|
||||
parent.right === node &&
|
||||
t.isIdentifier(parent.left)
|
||||
) {
|
||||
const statement = parents.get(parent);
|
||||
return t.isExpressionStatement(statement) && statement.expression === parent
|
||||
? statementPosition(statement, parents)
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const directMutationPosition = (
|
||||
node: t.Node,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
): StatementPosition | null => {
|
||||
if (
|
||||
!t.isAssignmentExpression(node) &&
|
||||
!t.isUpdateExpression(node) &&
|
||||
!t.isUnaryExpression(node, { operator: "delete" })
|
||||
)
|
||||
return null;
|
||||
const statement = parents.get(node);
|
||||
return t.isExpressionStatement(statement) && statement.expression === node
|
||||
? statementPosition(statement, parents)
|
||||
: null;
|
||||
};
|
||||
|
||||
const mutationInitializers = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
mutation: t.Node,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
): JavaScriptSemanticBindingState["initializers"] => {
|
||||
if (binding.initializers.length < 2) return binding.initializers;
|
||||
const mutationPosition = directMutationPosition(mutation, parents);
|
||||
if (mutationPosition === null) return binding.initializers;
|
||||
const positions = binding.initializers.map(({ node }) =>
|
||||
directInitializerPosition(node, parents),
|
||||
);
|
||||
if (
|
||||
positions.some(
|
||||
(position) =>
|
||||
position === null ||
|
||||
position.body !== mutationPosition.body ||
|
||||
position.index >= mutationPosition.index,
|
||||
)
|
||||
)
|
||||
return binding.initializers;
|
||||
const latestIndex = positions.reduce(
|
||||
(latest, position) => Math.max(latest, position?.index ?? -1),
|
||||
-1,
|
||||
);
|
||||
return binding.initializers.filter(
|
||||
(_, index) => positions[index]?.index === latestIndex,
|
||||
);
|
||||
};
|
||||
|
||||
interface ReferencedValue {
|
||||
readonly node: t.Node;
|
||||
readonly path: PropertyPath;
|
||||
|
|
@ -141,5 +241,7 @@ const referencedValues = (
|
|||
{ node: node.left, path },
|
||||
{ node: node.right, path },
|
||||
];
|
||||
if (t.isAssignmentExpression(node, { operator: "&&=" }))
|
||||
return [{ node: node.right, path }];
|
||||
return [];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { semanticSlotAtPath } from "./javascriptSemanticSlots.js";
|
|||
|
||||
import { invalidateSemanticMutationPath } from "./javascriptSemanticMutationValues.js";
|
||||
|
||||
import type { JavaScriptBindingProvenance } from "./javascriptSemanticIr.js";
|
||||
import type {
|
||||
JavaScriptBindingProvenance,
|
||||
JavaScriptSemanticProperty,
|
||||
JavaScriptSemanticResourceLimit,
|
||||
JavaScriptSemanticValue,
|
||||
} from "./javascriptSemanticIr.js";
|
||||
} from "./javascriptSemanticValueTypes.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
|
|
@ -17,7 +17,10 @@ import {
|
|||
} from "./javascriptSemanticState.js";
|
||||
import { semanticRequireOrigin } from "./javascriptSemanticRequireOrigin.js";
|
||||
import { compareCodePoints } from "../canonicalOrdering.js";
|
||||
import { semanticStaticPropertyKey } from "./javascriptAstValues.js";
|
||||
import {
|
||||
readExactJavaScriptLiteral,
|
||||
semanticStaticPropertyKey,
|
||||
} from "./javascriptAstValues.js";
|
||||
import {
|
||||
semanticAmbiguousProvenance,
|
||||
semanticLocalProvenance,
|
||||
|
|
@ -26,10 +29,8 @@ import {
|
|||
uniqueSemanticOrigins,
|
||||
} from "./javascriptSemanticProvenance.js";
|
||||
import {
|
||||
MAX_SEMANTIC_PRIMITIVE_CANDIDATES,
|
||||
semanticPrimitiveCandidates as primitiveCandidates,
|
||||
semanticPrimitiveSet as primitiveSet,
|
||||
semanticPrimitiveValue as primitiveValue,
|
||||
} from "./javascriptSemanticPrimitives.js";
|
||||
import {
|
||||
SEMANTIC_EXPRESSION_DEPTH_LIMIT,
|
||||
|
|
@ -120,6 +121,21 @@ const evaluateBinding = (
|
|||
status: "unknown",
|
||||
reason: `Binding ${binding.name} has no constant initializer.`,
|
||||
};
|
||||
if (
|
||||
binding.initializers.some(
|
||||
({ node }) =>
|
||||
t.isUpdateExpression(node) ||
|
||||
(t.isAssignmentExpression(node) &&
|
||||
node.operator !== "=" &&
|
||||
node.operator !== "&&=" &&
|
||||
node.operator !== "||=" &&
|
||||
node.operator !== "??="),
|
||||
)
|
||||
)
|
||||
return {
|
||||
status: "unknown",
|
||||
reason: `Binding ${binding.name} has a compound or update write.`,
|
||||
};
|
||||
if (binding.initializers.length > 1)
|
||||
return {
|
||||
status: "ambiguous",
|
||||
|
|
@ -154,7 +170,7 @@ const evaluateExpression = (
|
|||
): JavaScriptSemanticValue => {
|
||||
if (context.expressionDepth > SEMANTIC_EXPRESSION_DEPTH_LIMIT)
|
||||
return semanticResourceLimitUnknown("expression-depth");
|
||||
const literal = primitiveValue(node);
|
||||
const literal = readExactJavaScriptLiteral(node);
|
||||
if (literal.found) return { status: "literal", value: literal.value };
|
||||
if (t.isIdentifier(node)) {
|
||||
const binding = resolveSemanticBindingState(context.state, node, node.name);
|
||||
|
|
@ -408,7 +424,7 @@ const addPrimitiveValues = (
|
|||
if (isSemanticResourceLimit(rightValue)) return rightValue;
|
||||
return { status: "unknown", reason: "Non-primitive addition." };
|
||||
}
|
||||
if (left.length > MAX_SEMANTIC_PRIMITIVE_CANDIDATES / right.length)
|
||||
if (left.length > SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT / right.length)
|
||||
return semanticResourceLimitUnknown("primitive-candidates");
|
||||
if (exceedsSemanticPrimitiveAdditionByteBudget(left, right))
|
||||
return semanticResourceLimitUnknown("primitive-bytes");
|
||||
|
|
|
|||
Loading…
Reference in New Issue