fix(javascript)!: preserve operator influence in semantic traces (#7939)

* fix(javascript)!: preserve operator influence in semantic traces

* test(javascript): align semantic influence regressions

* style(javascript): format semantic IR

* test(javascript): follow literal argument seed influence

* fix(javascript): keep one literal node per operator operand

Reuse an existing literal for operator influence, and treat TypeScript
assertion wrappers as transparent when deciding whether a call result is used.

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* fix(javascript): index operator literals by range and initializer

Repeated string operands rescanned every retained literal and every
initializer flow. Lookup now uses those indexes, and a 1200-declaration
file still projects one literal per operand.

* fix(javascript): keep literal and call facts on their own occurrences

Operator literals match their own source range, so a nested function does
not inherit the enclosing declaration's literal. A direct argument still
reaches the call, while the assigned result follows modeled returns.
for-loop initializers and updates are discarded call results.

* test(javascript): expect conditional operand literals beside binding values

A conditional binding keeps one candidate literal per union value and one
operator literal per operand token. Those are different facts, so the
aggregate payload check counts four nodes and compares literal seeds in
query order.

* perf(javascript): index call return flows by call site

projectCalls filtered every call return for each assigned result. Group
those flows once while projecting returns-to-call edges.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-10-29 20:06:55 +08:00
1 parent 6fa48f201f
commit 5c05eb88e5
24 files changed
+1384 -39

No files matched your search

@@ -1,6 +1,7 @@
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
import { createSemanticNodeRangeLookup } from "./JavaScriptSemanticGraphProjection.js";
import { sourceRangesEqual } from "../../domain/javascript/javascriptStaticAnalysisHelpers.js";
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
import {
addSemanticGraphRelation,
addSemanticGraphUnknown,
@@ -13,6 +14,11 @@ export function* projectSemanticAssignments(
): Generator<void, void> {
const readsWithin = createSemanticNodeRangeLookup(context.readNodes);
const writesWithin = createSemanticNodeRangeLookup(context.writeNodes);
const expressions = new Set(
context.ir.expressionFlows.map((flow) =>
javascriptSourceRangeKey(flow.location),
),
);
for (const [index, flow] of context.ir.assignmentFlows.entries()) {
if (index % 64 === 0) yield;
const owner =
@@ -22,9 +28,11 @@ export function* projectSemanticAssignments(
const writes = writesWithin(flow.target).filter((node) =>
sourceRangesEqual(node.identity.source_range, flow.target),
);
const reads = readsWithin(flow.source).filter(
(node) => node.function_node_id === owner,
);
const reads = expressions.has(javascriptSourceRangeKey(flow.source))
? []
: readsWithin(flow.source).filter(
(node) => node.function_node_id === owner,
);
// Compound assignments consume the previous value as well as the RHS.
if (flow.operator !== "=")
reads.push(
@@ -31,7 +31,7 @@ import { getterExpansionSource } from "../../../tests/fixtures/javascriptGetterE
const SHA256 = "a".repeat(64);
it("retains direct call literals and resolves literal queries to their call site", () => {
it("retains direct call literals and resolves literal queries to their argument", () => {
const source =
'api.rpc("proc_0", 7, false, null, dynamic, ...values, `fixed`, -3);';
const graph = graphFor(source);
@@ -57,7 +57,10 @@ it("retains direct call literals and resolves literal queries to their call site
direction: "forward-influence",
}),
);
expect(trace.seed_node_ids).toContain(call.node_id);
expect(
trace.nodes.find((node) => node.node_id === trace.seed_node_ids[0])?.label,
).toBe("argument 0");
expect(trace.nodes.some((node) => node.node_id === call.node_id)).toBe(true);
});
it("reports the primitive byte bound when a direct argument is too large", () => {
@@ -14,9 +14,11 @@ import {
} from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import type {
JavaScriptSemanticCallArgument,
JavaScriptSemanticCallReturnFlow,
JavaScriptSemanticIr,
} from "../../domain/javascript/javascriptSemanticIr.js";
import { sourceRangesEqual as rangesEqual } from "../../domain/javascript/javascriptStaticAnalysisHelpers.js";
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
import type { JavaScriptApplicationGraph } from "../../domain/javascript/javascriptApplicationGraph.js";
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
import {
@@ -31,6 +33,7 @@ import {
} from "./JavaScriptSemanticGraphDataProjection.js";
import { projectSemanticResources } from "./JavaScriptSemanticGraphResourceProjection.js";
import { projectSemanticAssignments } from "./JavaScriptSemanticGraphAssignmentProjection.js";
import { projectSemanticExpressionInfluence } from "./JavaScriptSemanticGraphExpressionProjection.js";
import { projectSemanticObjects } from "./JavaScriptSemanticGraphObjectProjection.js";
import { projectSemanticValues } from "./JavaScriptSemanticGraphValueProjection.js";
import {
@@ -935,6 +938,11 @@ function* projectDefinitionsAndReferences(
}
const projectCalls = (context: FileContext): void => {
const expressions = new Set(
context.ir.expressionFlows.map((flow) =>
javascriptSourceRangeKey(flow.location),
),
);
for (const call of context.ir.callSites) {
const callNode = context.callSiteNodes.get(call.callSiteId);
if (callNode === undefined) continue;
@@ -959,7 +967,40 @@ const projectCalls = (context: FileContext): void => {
`${call.callSiteId}\u0000${String(argument.index)}`,
argumentNode,
);
const references = context.referenceNodesWithin(argument.location);
if (!argument.spread && Object.hasOwn(argument, "literal"))
addRelation(context.state, {
source: argumentNode,
target: callNode,
relation: "influences",
resolution: "resolved",
});
if (
call.resolution !== "exact" &&
call.resultUsed &&
argumentNode !== null
)
addSemanticGraphUnknown(context.state, {
node_id: argumentNode.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "dynamic-call",
detail:
"The dynamic call's return dependency on this argument is unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, argument.location),
});
const references =
argument.valueExpressionLocation !== null &&
expressions.has(
javascriptSourceRangeKey(argument.valueExpressionLocation),
)
? []
: context
.referenceNodesWithin(argument.location)
.filter(
(reference) =>
reference.function_node_id === callNode.function_node_id,
);
for (const reference of references)
addRelation(context.state, {
source: reference,
@@ -994,20 +1035,46 @@ const projectCalls = (context: FileContext): void => {
resolution: context.callResolutions.get(flow.callSiteId) ?? "candidate",
});
}
for (const flow of context.ir.callReturnFlows)
const returnFlowsByCallSite = new Map<
string,
JavaScriptSemanticCallReturnFlow[]
>();
for (const flow of context.ir.callReturnFlows) {
const grouped = returnFlowsByCallSite.get(flow.callSiteId);
if (grouped === undefined)
returnFlowsByCallSite.set(flow.callSiteId, [flow]);
else grouped.push(flow);
addRelation(context.state, {
source: context.returnSiteNodes.get(flow.returnSiteId),
target: context.callSiteNodes.get(flow.callSiteId),
relation: "returns-to-call",
resolution: context.callResolutions.get(flow.callSiteId) ?? "candidate",
});
for (const flow of context.ir.callResultFlows)
addRelation(context.state, {
source: context.callSiteNodes.get(flow.callSiteId),
target: context.bindingNodes.get(flow.bindingId),
relation: "aliases",
resolution: context.callResolutions.get(flow.callSiteId) ?? "candidate",
});
}
for (const flow of context.ir.callResultFlows) {
const resolution =
context.callResolutions.get(flow.callSiteId) ?? "candidate";
const target = context.bindingNodes.get(flow.bindingId);
const returns = returnFlowsByCallSite.get(flow.callSiteId) ?? [];
// A literal argument may influence the invocation. The assigned value
// comes from modeled returns, so an ignored parameter does not reach it.
if (returns.length === 0) {
addRelation(context.state, {
source: context.callSiteNodes.get(flow.callSiteId),
target,
relation: "aliases",
resolution,
});
continue;
}
for (const item of returns)
addRelation(context.state, {
source: context.returnSiteNodes.get(item.returnSiteId),
target,
relation: "aliases",
resolution,
});
}
projectSemanticReturnValues(context);
};
@@ -1025,6 +1092,14 @@ const createArgumentNode = (
? "spread argument"
: `argument ${String(argument.index)}`,
functionNodeId: callNode.function_node_id,
...(!argument.spread && Object.hasOwn(argument, "literal")
? {
properties: {
literal_argument_value: argument.literal ?? null,
literal_argument_call_site_node_id: callNode.node_id,
},
}
: {}),
});
/** Per-file projection passes, run in this order with a yield between each. */
@@ -1035,6 +1110,7 @@ const FILE_PROJECTION_PASSES: readonly ((
projectSemanticObjects,
projectSemanticAssignments,
projectCalls,
projectSemanticExpressionInfluence,
projectSemanticPromises,
projectSemanticEvents,
projectSemanticTimers,
@@ -0,0 +1,356 @@
import { expect, it } from "vitest";
import { graphForJavaScript } from "../../../tests/fixtures/javascriptSemanticGraph.js";
import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js";
import { resolveJavaScriptSemanticQuerySeeds } from "../../domain/javascript/javascriptSemanticQueryIdentity.js";
import { javaScriptSemanticQueryInputSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js";
import { parseJavaScriptSource } from "../../domain/javascript/javascriptSourceParser.js";
import { analyzeParsedJavaScriptSemanticsSteps } from "../../domain/javascript/javascriptSemanticAnalysis.js";
import { completeSemanticSteps } from "../../domain/javascript/javascriptSemanticTraversal.js";
const trace = (body: string) => {
const graph = graphForJavaScript(
`const base = "https://example.test"; ${body}`,
);
return queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "https://example.test" },
direction: "forward-influence",
}),
);
};
it.each([
["declaration", 'const url = base + "/x"; fetch(url);'],
["assignment", 'let url; url = base + "/x"; fetch(url);'],
["nested operator", 'const url = (base + "/x") + "/y"; fetch(url);'],
["call argument", 'fetch(base + "/x");'],
["template", "fetch(`${base}/x`);"],
])("follows transformed input to a %s without opt-in", (_name, body) => {
const result = trace(body);
expect(result.nodes.map((node) => node.label)).toContain("argument 0");
expect(result.relations.some((edge) => edge.relation === "influences")).toBe(
true,
);
});
it("explains an unmodeled dynamic call result at the reached argument", () => {
const result = trace('const url = wrap(base + "/x"); fetch(url);');
expect(result.unknowns.map((unknown) => unknown.detail)).toContain(
"The dynamic call's return dependency on this argument is unknown.",
);
});
it.each([
'const obj = { url: wrap(base + "/x") }; fetch(obj.url);',
'function address() { return wrap(base + "/x"); }',
'consume(wrap(base + "/x"));',
])("keeps nested dynamic call return influence unknown: %s", (body) => {
const result = trace(body);
expect(result.status).not.toBe("found");
expect(result.unknowns.map((unknown) => unknown.detail)).toContain(
"The dynamic call's return dependency on this argument is unknown.",
);
});
it("does not report an unused dynamic call result as a missing value", () => {
const result = trace('consume(base + "/x");');
expect(
result.unknowns.some(
(unknown) =>
unknown.detail ===
"The dynamic call's return dependency on this argument is unknown.",
),
).toBe(false);
});
it("bounds operator production before graph admission and reports truncation", () => {
const expressions = Array.from({ length: 500 }, () => "${base}").join("");
const source = `const base = "value"; const value = \`${expressions}\`;`;
const parsed = parseJavaScriptSource(source, "app.js");
if (parsed === null) throw new Error("Expected parsed source");
const ir = completeSemanticSteps(
analyzeParsedJavaScriptSemanticsSteps(parsed, "import", 4),
);
expect(ir.expressionFlowsTruncated).toBe(true);
expect(ir.expressionFlows.length).toBeLessThanOrEqual(4);
expect(
ir.expressionFlows.reduce((count, flow) => count + flow.operands.length, 0),
).toBeLessThanOrEqual(4);
expect(ir.coverage).toMatchObject({ status: "partial", omittedCount: null });
expect(graphForJavaScript(source, ir).coverage.truncated).toBe(true);
});
it("follows a transformed return expression", () => {
const result = trace('function address() { return base + "/x"; }');
expect(result.nodes.map((node) => node.label)).toContain("return");
});
it("does not treat a deferred callback body as an enclosing call argument value", () => {
const graph = graphForJavaScript(
'const base = "https://example.test"; consume(() => base + "/x");',
);
const base = graph.nodes.find(
(node) => node.kind === "binding" && node.label === "base",
);
const argument = graph.nodes.find(
(node) => node.kind === "expression" && node.label === "argument 0",
);
expect(base).toBeDefined();
expect(argument).toBeDefined();
const result = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "https://example.test" },
direction: "forward-influence",
include_ambiguous_dynamic_edges: true,
}),
);
expect(result.nodes.map((node) => node.node_id)).not.toContain(
argument?.node_id,
);
});
it.each(['"/unique"', "`/unique`"])(
"seeds an exact literal operand %s and reaches the receiving argument",
(operand) => {
const graph = graphForJavaScript(
`const prefix = "root"; const path = prefix + ${operand}; fetch(path);`,
);
const result = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "/unique" },
direction: "forward-influence",
}),
);
expect(result.nodes.map((node) => node.label)).toContain("argument 0");
},
);
it("marks a short-circuited operand as conditional without claiming alias identity", () => {
const graph = graphForJavaScript(
'const base = "unique"; const maybe = ready && base; fetch(maybe);',
);
const conditional = graph.relations.filter(
(edge) =>
edge.relation === "influences" && edge.properties.conditional === true,
);
expect(conditional).toHaveLength(1);
expect(
graph.nodes.find((node) => node.node_id === conditional[0]?.source_node_id)
?.label,
).toBe("base");
});
it("reports uncertainty on a read inside an unsupported operand", () => {
const result = trace('const url = format(base) + "/x"; fetch(url);');
expect(result.status).toBe("partial");
expect(
result.unknowns.some((unknown) =>
unknown.detail.includes("unmodeled operator operand"),
),
).toBe(true);
});
it.each([
'function answer() { return "return-only"; }',
'const answer = () => "return-only";',
])("seeds a literal returned directly: %s", (source) => {
const graph = graphForJavaScript(source);
const result = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "return-only" },
direction: "forward-influence",
}),
);
expect(result.summary.total_seed_matches).toBeGreaterThan(0);
expect(result.nodes.map((node) => node.label)).toContain("return");
});
it("marks a logical assignment's transformed input conditional", () => {
const graph = graphForJavaScript(
'const base = "unique"; let value; value ||= base + "/x";',
);
const conditional = graph.relations.find(
(edge) =>
edge.relation === "influences" &&
edge.properties.conditional === true &&
graph.nodes.find((node) => node.node_id === edge.target_node_id)
?.label === "value",
);
expect(conditional).toBeDefined();
});
it("keeps sequence side effects unknown instead of attributing earlier values directly", () => {
const result = trace('const url = (touch(base), "fixed"); fetch(url);');
expect(result.status).toBe("partial");
expect(
result.unknowns.some((unknown) =>
unknown.detail.includes("unmodeled operator operand"),
),
).toBe(true);
});
it("seeds a direct argument at its occurrence and reaches the call", () => {
const graph = graphForJavaScript('consume("only-direct");');
const result = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "only-direct" },
direction: "forward-influence",
}),
);
expect(result.summary.total_seed_matches).toBe(1);
expect(
result.nodes.find((node) => node.node_id === result.seed_node_ids[0])
?.label,
).toBe("argument 0");
expect(result.nodes.some((node) => node.kind === "call-site")).toBe(true);
});
it("keeps older call-site literal evidence searchable when argument literals are absent", () => {
const graph = graphForJavaScript('consume("only-direct");');
const legacy = {
...graph,
nodes: graph.nodes.filter(
(node) =>
node.properties.literal_argument_call_site_node_id === undefined,
),
};
const seeds = resolveJavaScriptSemanticQuerySeeds(
legacy,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "only-direct" },
direction: "forward-influence",
}),
);
const call = graph.nodes.find((node) => node.kind === "call-site");
expect(seeds).toEqual([call?.node_id]);
});
it("does not treat literals inside a spread or deferred callback as direct arguments", () => {
const graph = graphForJavaScript(
'consume(...["spread-only"], () => "deferred-only");',
);
for (const value of ["spread-only", "deferred-only"]) {
const result = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value },
direction: "forward-influence",
}),
);
expect(
result.nodes.filter(
(node) =>
node.properties.literal_argument_call_site_node_id !== undefined,
),
).toEqual([]);
}
});
it.each([
'const obj = { url: base + "/x" }; fetch(obj.url);',
'const values = [base + "/x"]; fetch(values[0]);',
'const url = wrap(base + "/x"); fetch(url);',
])("reports unknown when a consumer boundary is unmodeled: %s", (body) => {
const result = trace(body);
expect(result.status).not.toBe("found");
expect(result.unknowns.length).toBeGreaterThan(0);
});
it("does not reuse an outer literal for the same text in a nested function", () => {
const graph = graphForJavaScript(
'const suffix = "end"; const value = true ? "token" : () => "token" + suffix;',
);
const tokens = graph.nodes.filter(
(node) => node.kind === "literal" && node.properties.value === "token",
);
expect(tokens.length).toBeGreaterThan(1);
const inner = tokens.find((node) => node.function_node_id !== null);
const outer = tokens.find((node) => node.function_node_id === null);
if (inner === undefined || outer === undefined)
throw new Error("Expected separate outer and nested token literals");
expect(inner.identity.source_range).not.toEqual(outer.identity.source_range);
const traced = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "semantic-node", node_id: outer.node_id },
direction: "forward-influence",
}),
);
expect(traced.nodes.map((node) => node.node_id)).not.toContain(inner.node_id);
});
it("does not send an ignored literal argument to the call result", () => {
const graph = graphForJavaScript(
'function ignore(x) { return "safe"; } const result = ignore("token");',
);
const resultBinding = graph.nodes.find(
(node) => node.kind === "binding" && node.label === "result",
);
if (resultBinding === undefined) throw new Error("Expected result binding");
const forward = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "token" },
direction: "forward-influence",
}),
);
expect(forward.nodes.some((node) => node.kind === "call-site")).toBe(true);
expect(forward.nodes.map((node) => node.node_id)).not.toContain(
resultBinding.node_id,
);
const backward = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "semantic-node", node_id: resultBinding.node_id },
direction: "backward-provenance",
}),
);
expect(
backward.nodes.some(
(node) =>
node.properties.value === "token" ||
node.properties.literal_argument_value === "token",
),
).toBe(false);
expect(backward.nodes.some((node) => node.properties.value === "safe")).toBe(
true,
);
});
it("keeps conditional binding candidates distinct from operand occurrences", () => {
const graph = graphForJavaScript('export const choice = flag ? 1 : "1";');
const choices = graph.nodes.filter(
(node) =>
node.kind === "literal" &&
(node.properties.value === 1 || node.properties.value === "1"),
);
expect(choices).toHaveLength(4);
expect(
new Set(
choices.map((node) =>
JSON.stringify([node.properties.value, node.identity.source_range]),
),
).size,
).toBe(4);
});
it("projects repeated operator literals with indexed lookup", () => {
const count = 1_200;
const declarations = Array.from(
{ length: count },
(_, index) => `const item${String(index)} = input + "/item";`,
);
const graph = graphForJavaScript(
`const input = "root";\n${declarations.join("\n")}`,
);
expect(
graph.nodes.filter(
(node) => node.kind === "literal" && node.properties.value === "/item",
),
).toHaveLength(count);
});
@@ -0,0 +1,396 @@
import {
compareJavaScriptSourcePoints,
javascriptSourceRangeContains,
javascriptSourceRangeKey,
} from "../../domain/javascript/javascriptSourceCoordinates.js";
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
import {
addSemanticGraphRelation,
addSemanticGraphUnknown,
retainSemanticGraphNode,
} from "./JavaScriptSemanticGraphConstruction.js";
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
const sameArtifact = (
node: JavaScriptSemanticGraphNode,
context: SemanticFlowProjectionContext,
): boolean =>
node.identity.module_path === context.file.path &&
node.identity.artifact_sha256 === context.file.sha256;
const literalValueKey = (value: string | number | boolean | null): string =>
`${typeof value}:${JSON.stringify(value)}`;
type IndexedOperatorLiteral = {
readonly node: JavaScriptSemanticGraphNode;
readonly range: JavaScriptSourceRange;
readonly order: number;
};
/**
* Literals are remembered by their own source range and value.
* An initializer that merely contains another occurrence, including a
* nested function body, does not reuse the declaration's literal.
*/
const operatorLiteralIndex = (
context: SemanticFlowProjectionContext,
): {
remember(
node: JavaScriptSemanticGraphNode | null,
): JavaScriptSemanticGraphNode | null;
find(
value: string | number | boolean | null,
location: JavaScriptSourceRange,
): JavaScriptSemanticGraphNode | undefined;
} => {
const exact = new Map<string, IndexedOperatorLiteral>();
const byValue = new Map<string, IndexedOperatorLiteral[]>();
const seen = new Set<string>();
let order = 0;
const remember = (
node: JavaScriptSemanticGraphNode | null,
): JavaScriptSemanticGraphNode | null => {
if (
node === null ||
node.kind !== "literal" ||
!sameArtifact(node, context) ||
seen.has(node.node_id)
)
return node;
const range = node.identity.source_range;
const value = node.properties.value;
if (
range === null ||
(typeof value !== "string" &&
typeof value !== "number" &&
typeof value !== "boolean" &&
value !== null)
)
return node;
seen.add(node.node_id);
const entry = { node, range, order: order++ };
const id = `${literalValueKey(value)}\0${javascriptSourceRangeKey(range)}`;
const group = byValue.get(literalValueKey(value)) ?? [];
group.push(entry);
byValue.set(literalValueKey(value), group);
if (!exact.has(id)) exact.set(id, entry);
return node;
};
for (const node of context.state.nodes.values()) remember(node);
return {
remember,
find(value, location) {
const valueKey = literalValueKey(value);
const operandKey = javascriptSourceRangeKey(location);
let best: IndexedOperatorLiteral | undefined;
const consider = (entry: IndexedOperatorLiteral | undefined): void => {
if (
entry !== undefined &&
(best === undefined || entry.order < best.order)
)
best = entry;
};
consider(exact.get(`${valueKey}\0${operandKey}`));
const group = byValue.get(valueKey) ?? [];
let low = 0;
let high = group.length;
while (low < high) {
const middle = (low + high) >> 1;
const entry = group[middle];
if (
entry !== undefined &&
compareJavaScriptSourcePoints(entry.range.start, location.start) <= 0
)
low = middle + 1;
else high = middle;
}
const candidate = group[low - 1];
if (
candidate !== undefined &&
javascriptSourceRangeContains(candidate.range, location)
)
consider(candidate);
return best?.node;
},
};
};
/** Project immediate operator dependencies without treating them as aliases. */
export function* projectSemanticExpressionInfluence(
context: SemanticFlowProjectionContext & {
readonly argumentNodes: ReadonlyMap<string, JavaScriptSemanticGraphNode>;
},
): Generator<void, void> {
if (context.ir.expressionFlowsTruncated)
context.state.fileNodesDropped = true;
const key = javascriptSourceRangeKey;
const expressions = new Map<string, JavaScriptSemanticGraphNode>();
const usedExpressions = new Set<string>();
const reads = new Map(
context.readNodes.flatMap((node) =>
node.identity.source_range === null
? []
: [[key(node.identity.source_range), node] as const],
),
);
const writes = new Map(
context.writeNodes.flatMap((node) =>
node.identity.source_range === null
? []
: [[key(node.identity.source_range), node] as const],
),
);
const bindings = new Map(
context.ir.bindings.flatMap((binding) => {
const node = context.bindingNodes.get(binding.bindingId);
return node === undefined
? []
: binding.definitions.map(
(definition) => [key(definition.location), node] as const,
);
}),
);
for (const [index, flow] of context.ir.expressionFlows.entries()) {
if (index % 64 === 0) yield;
const owner =
flow.ownerCallableId === null
? null
: (context.callableNodes.get(flow.ownerCallableId)?.node_id ?? null);
const node = retainSemanticGraphNode(context.state, context.file, {
kind: "expression",
roleKey: `operator:${index}`,
location: flow.location,
label: flow.operator,
functionNodeId: owner,
properties: { operator: flow.operator },
});
if (node !== null) expressions.set(key(flow.location), node);
}
const remainingUnknowns =
context.state.unknownCapacity === null
? context.readNodes.length
: Math.max(
0,
context.state.unknownCapacity - context.state.unknowns.size,
);
let unsupportedReads = 0;
let unsupportedOverflow = false;
const orderedReads = context.readNodes
.flatMap((node) =>
node.identity.source_range === null
? []
: [{ node, location: node.identity.source_range }],
)
.sort((left, right) =>
compareJavaScriptSourcePoints(left.location.start, right.location.start),
);
let processedOperands = 0;
let processedReferences = 0;
const literals = operatorLiteralIndex(context);
for (const [index, flow] of context.ir.expressionFlows.entries()) {
if (index % 64 === 0) yield;
const result = expressions.get(key(flow.location));
for (const operand of flow.operands) {
if (processedOperands % 64 === 0) yield;
processedOperands += 1;
if (operand.kind === "unsupported") {
if (result !== undefined)
addSemanticGraphUnknown(context.state, {
node_id: result.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "unknown-value",
detail: "An operator operand has unmodeled value dependencies.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, operand.location),
});
if (!unsupportedOverflow) {
let low = 0,
high = orderedReads.length;
while (low < high) {
const middle = Math.floor((low + high) / 2);
if (
compareJavaScriptSourcePoints(
orderedReads[middle]!.location.start,
operand.location.start,
) < 0
)
low = middle + 1;
else high = middle;
}
for (let offset = low; offset < orderedReads.length; offset++) {
const { node: reference, location } = orderedReads[offset]!;
if (
compareJavaScriptSourcePoints(
location.start,
operand.location.end,
) > 0
)
break;
if (processedReferences % 64 === 0) yield;
processedReferences += 1;
if (processedReferences > remainingUnknowns) {
unsupportedOverflow = true;
break;
}
if (
!javascriptSourceRangeContains(operand.location, location) ||
reference.function_node_id !== result?.function_node_id
)
continue;
if (unsupportedReads >= remainingUnknowns) {
unsupportedOverflow = true;
break;
}
unsupportedReads += 1;
addSemanticGraphUnknown(context.state, {
node_id: reference.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "unknown-value",
detail:
"This read occurs inside an unmodeled operator operand; its influence on the result is unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, operand.location),
});
}
}
continue;
}
const source =
operand.kind === "reference"
? reads.get(key(operand.location))
: operand.kind === "expression"
? expressions.get(key(operand.location))
: (literals.find(operand.literal ?? null, operand.location) ??
literals.remember(
retainSemanticGraphNode(context.state, context.file, {
kind: "literal",
roleKey: `operator-literal:${key(operand.location)}:${JSON.stringify(operand.literal ?? null)}`,
location: operand.location,
label: "operator literal",
functionNodeId: result?.function_node_id ?? null,
properties: { value: operand.literal ?? null },
}) ?? null,
));
if (
operand.kind === "expression" &&
source !== undefined &&
result !== undefined
)
usedExpressions.add(key(operand.location));
addSemanticGraphRelation(context.state, {
source,
target: result,
relation: "influences",
resolution: "resolved",
properties: { conditional: operand.conditional },
});
}
}
// Some operands are calls, members or other constructs whose value owner is
// not yet modeled here. Do not claim complete influence for any query that
// may cross one of these unmodeled operator inputs.
if (unsupportedOverflow)
addSemanticGraphUnknown(context.state, {
node_id: null,
family: "data-flow",
relation_kinds: ["influences"],
reason: "resource-limit",
detail:
"The bounded operator-dependency audit omitted some unmodeled operand reads; influence through them remains unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, null),
});
const connect = (
source: JavaScriptSemanticGraphNode | undefined,
target: JavaScriptSemanticGraphNode | undefined,
conditional = false,
) => {
if (
source !== undefined &&
target !== undefined &&
source.identity.source_range !== null &&
expressions.get(key(source.identity.source_range)) === source
)
usedExpressions.add(key(source.identity.source_range));
addSemanticGraphRelation(context.state, {
source,
target,
relation: "influences",
resolution: "resolved",
properties: { conditional },
});
};
for (const flow of context.ir.initializerFlows)
connect(expressions.get(key(flow.source)), bindings.get(key(flow.target)));
for (const flow of context.ir.assignmentFlows) {
const source = expressions.get(key(flow.source));
const target = writes.get(key(flow.target));
if (source !== undefined && target !== undefined)
usedExpressions.add(key(flow.source));
addSemanticGraphRelation(context.state, {
source,
target,
relation: "influences",
resolution: "resolved",
properties: {
conditional: ["&&=", "||=", "??="].includes(flow.operator),
assignment_operator: flow.operator,
},
});
}
for (const call of context.ir.callSites)
for (const argument of call.arguments) {
if (argument.valueExpressionLocation === null) continue;
connect(
expressions.get(key(argument.valueExpressionLocation)),
context.argumentNodes.get(
`${call.callSiteId}\u0000${String(argument.index)}`,
),
);
}
const literalReturns = new Map(
context.ir.literalReturns.map((item) => [key(item.location), item]),
);
for (const callable of context.ir.callables)
for (const site of callable.returnSites) {
if (site.valueExpressionLocation !== null) {
const target = context.returnSiteNodes.get(site.returnSiteId);
connect(expressions.get(key(site.valueExpressionLocation)), target);
const literal = literalReturns.get(key(site.valueExpressionLocation));
if (literal !== undefined) {
const source = retainSemanticGraphNode(context.state, context.file, {
kind: "literal",
roleKey: `return-literal:${site.returnSiteId}`,
location: literal.location,
label: "return literal",
functionNodeId: target?.function_node_id ?? null,
properties: { value: literal.value },
});
addSemanticGraphRelation(context.state, {
source,
target,
relation: "aliases",
resolution: "resolved",
});
}
}
}
for (const flow of context.ir.expressionFlows) {
const node = expressions.get(key(flow.location));
if (node === undefined || usedExpressions.has(key(flow.location))) continue;
addSemanticGraphUnknown(context.state, {
node_id: node.node_id,
family: "data-flow",
relation_kinds: ["influences"],
reason: "unknown-value",
detail:
"The receiving expression or container for this operator result is not modeled; further influence is unknown.",
candidate_node_ids: [],
evidence: unknownSemanticEvidence(context.file, flow.location),
});
}
}
@@ -269,10 +269,24 @@ export const semanticObjectReadOccurrence = (
export const projectSemanticReturnValues = (
context: SemanticFlowProjectionContext,
): void => {
const expressions = new Set(
context.ir.expressionFlows.map((flow) =>
javascriptSourceRangeKey(flow.location),
),
);
for (const callable of context.ir.callables)
for (const site of callable.returnSites) {
const returnNode = context.returnSiteNodes.get(site.returnSiteId);
const references = context.referenceNodesWithin(site.location);
const references =
site.valueExpressionLocation !== null &&
expressions.has(javascriptSourceRangeKey(site.valueExpressionLocation))
? []
: context
.referenceNodesWithin(site.location)
.filter(
(reference) =>
reference.function_node_id === returnNode?.function_node_id,
);
for (const reference of references)
addSemanticGraphRelation(context.state, {
source: reference,
@@ -360,7 +360,13 @@ it("does not promote transformed return expressions to resolved aliases", () =>
({ relation, target_node_id }) =>
relation === "aliases" && target_node_id === returnNode.node_id,
),
).toEqual([expect.objectContaining({ resolution: "candidate" })]);
).toEqual([]);
expect(
graph.relations.filter(
({ relation, target_node_id }) =>
relation === "influences" && target_node_id === returnNode.node_id,
),
).toEqual([expect.objectContaining({ resolution: "resolved" })]);
const output = graph.nodes.find(
({ kind, label }) => kind === "binding" && label === "output",
);
@@ -376,10 +382,8 @@ it("does not promote transformed return expressions to resolved aliases", () =>
direction: "backward-provenance",
}),
);
expect(strict.nodes.map(({ node_id }) => node_id)).not.toContain(
input.node_id,
);
expect(strict.status).toBe("ambiguous");
expect(strict.nodes.map(({ node_id }) => node_id)).toContain(input.node_id);
expect(strict.status).toBe("partial");
});
it("uses unavailable evidence when no source produced semantic IR", () => {
@@ -318,3 +318,25 @@ it("applies the same unknown admission in direct and transferred construction",
transferred.finish(digest, application),
);
});
it("bounds reads within unmodeled nested operator operands", () => {
const body = `const base = "token"; const value = ${"wrap(".repeat(30)}base${")".repeat(30)} + "/x";`;
const bounded = fileProjection(source("app.js", body), 200, 1);
expect(bounded.unknowns.length).toBeLessThanOrEqual(1);
expect(bounded.omittedUnknownOccurrences?.length).toBeGreaterThan(0);
const projection = createJavaScriptSemanticGraphProjection({
totalUnknowns: 1,
fileUnknowns: 1,
});
projection.adoptFileProjection(bounded);
const graph = projection.finish(digest, application);
expect(graph.coverage.truncated).toBe(true);
const trace = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "token" },
direction: "forward-influence",
}),
);
expect(trace.status).not.toBe("found");
});