feat(javascript): discover semantic call literals in feature traces (#7840)

* feat(javascript): discover semantic call literals in feature traces

* fix(javascript): bound retained call literals by transfer capacity

* test(javascript): align feature no-match diagnostic
This commit is contained in:
morluto authored and GitHub committed 2026-10-29 01:25:23 +08:00
1 parent 685f6db960
commit f9e7571a3d
20 files changed
+681 -15

No files matched your search

@@ -158,6 +158,10 @@ export const traceApplicationFeatureEvidence = (
const result = traceApplicationFeature({
sourceEvidenceId: source.evidence.evidence_id,
graph: source.graph,
semanticGraph: source.semanticGraph,
...(input.semantic_page === undefined
? {}
: { semanticPage: input.semantic_page }),
nativeEvidence,
seed: input.seed,
direction: input.direction,
@@ -168,6 +172,9 @@ export const traceApplicationFeatureEvidence = (
native_evidence_ids: nativeEvidence.map(({ evidence_id: id }) => id),
seed: input.seed,
direction: input.direction,
...(input.semantic_page === undefined
? {}
: { semantic_page: input.semantic_page }),
},
result,
);
@@ -31,6 +31,99 @@ import { getterExpansionSource } from "../../../tests/fixtures/javascriptGetterE
const SHA256 = "a".repeat(64);
it("retains direct call literals and resolves literal queries to their call site", () => {
const source =
'api.rpc("proc_0", 7, false, null, dynamic, ...values, `fixed`, -3);';
const graph = graphFor(source);
const call = graph.nodes.find(({ kind }) => kind === "call-site");
const argumentsValue = call?.properties.arguments;
expect(argumentsValue).toEqual([
expect.objectContaining({
index: 0,
value: "proc_0",
source_range: expect.any(Object),
}),
expect.objectContaining({ index: 1, value: 7 }),
expect.objectContaining({ index: 2, value: false }),
expect.objectContaining({ index: 3, value: null }),
expect.objectContaining({ index: 6, value: "fixed" }),
expect.objectContaining({ index: 7, value: -3 }),
]);
if (call === undefined) throw new Error("Expected call site");
const trace = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "proc_0" },
direction: "forward-influence",
}),
);
expect(trace.seed_node_ids).toContain(call.node_id);
});
it("reports the primitive byte bound when a direct argument is too large", () => {
const source = `rpc(${JSON.stringify("x".repeat(175_000))});`;
const ir = analyzeJavaScriptSemantics(source);
expect(ir.coverage.resourceLimits).toContain("primitive-bytes");
const graph = graphFor(source, ir);
const call = graph.nodes.find(({ kind }) => kind === "call-site");
expect(call?.properties.arguments).toBeUndefined();
expect(graph.coverage.status).not.toBe("complete");
const unknown = graph.unknowns.find(
(entry) =>
entry.node_id === call?.node_id && entry.reason === "resource-limit",
);
expect(unknown?.detail).toMatch(/primitive string-byte budget exceeded/i);
expect(
graph.evidence_contexts.find(
(entry) => entry.context_id === unknown?.evidence.context_id,
),
).toMatchObject({
coverage: {
limits: expect.arrayContaining([
expect.objectContaining({
name: "javascript_semantic_primitive_bytes",
}),
]),
},
});
});
it("keeps a large call's graph within the worker record limit and reports omitted arguments", () => {
const value = JSON.stringify("x".repeat(140_000));
const source = `rpc(${Array.from({ length: 64 }, () => value).join(",")});`;
const graph = graphFor(source);
const call = graph.nodes.find(({ kind }) => kind === "call-site");
if (call === undefined) throw new Error("Expected call site");
const values = call.properties.arguments;
expect(Array.isArray(values)).toBe(true);
if (!Array.isArray(values)) throw new Error("Expected retained arguments");
expect(values.length).toBeGreaterThan(0);
expect(values.length).toBeLessThan(64);
expect(
Buffer.byteLength(`${JSON.stringify({ kind: "node", value: call })}\n`),
).toBeLessThanOrEqual(8 * 1024 * 1024);
const unknown = graph.unknowns.find(
(entry) =>
entry.node_id === call.node_id &&
entry.detail.includes("worker transfer record ceiling"),
);
expect(unknown).toMatchObject({ reason: "resource-limit" });
expect(
graph.evidence_contexts.find(
(entry) => entry.context_id === unknown?.evidence.context_id,
),
).toMatchObject({
coverage: {
limits: expect.arrayContaining([
expect.objectContaining({
name: "javascript_analysis_transfer_record_bytes",
value: 8 * 1024 * 1024,
}),
]),
},
});
});
it("projects bounded alias selection as a resource-limit unknown", () => {
const source = aliasSelectionBudgetSource();
const ir = analyzeJavaScriptSemantics(source);
@@ -51,6 +51,7 @@ import {
import type { JsonValue } from "../../domain/jsonValue.js";
import {
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS,
JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES,
JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM,
javaScriptAnalysisCoverageLimits,
javaScriptSemanticCeilingCoverage,
@@ -71,6 +72,7 @@ import {
semanticFamilyStatus,
} from "./JavaScriptSemanticGraphProjection.js";
import {
constructSemanticGraphNode,
addSemanticFallbackRoot as addFallbackRoot,
addSemanticGraphUnknown,
recordSemanticUnknownOmission,
@@ -771,14 +773,102 @@ function* createCallSiteNodes(
call.callerCallableId === null
? null
: (callables.get(call.callerCallableId)?.node_id ?? null);
const node = retainNode(state, file, {
kind: "call-site",
const nodeInput = {
kind: "call-site" as const,
roleKey: `call:${call.callSiteId}`,
location: call.location,
label: call.kind,
functionNodeId: owner,
};
// Include the complete node and worker JSON envelope before retaining any
// arguments. Each entry is charged by its encoded size and comma, so even
// worst-case escaping cannot overflow the worker's single-record ceiling.
let recordBytes = 0;
if (call.arguments.some((argument) => Object.hasOwn(argument, "literal"))) {
const baseline = constructSemanticGraphNode(file, nodeInput, state);
recordBytes = Buffer.byteLength(
`${JSON.stringify({ kind: "node", value: { ...baseline, properties: { arguments: [] } } })}\n`,
);
}
const argumentsValue: JsonValue[] = [];
let transferOmitted = 0;
for (const argument of call.arguments) {
if (!Object.hasOwn(argument, "literal")) continue;
const item = {
index: argument.index,
value: argument.literal ?? null,
source_range:
argument.valueExpressionLocation === null
? null
: {
start: { ...argument.valueExpressionLocation.start },
end: { ...argument.valueExpressionLocation.end },
},
};
const size =
Buffer.byteLength(JSON.stringify(item)) +
(argumentsValue.length === 0 ? 0 : 1);
if (recordBytes + size > JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES) {
transferOmitted += 1;
continue;
}
argumentsValue.push(item);
recordBytes += size;
}
const node = retainNode(state, file, {
...nodeInput,
...(argumentsValue.length === 0
? {}
: { properties: { arguments: argumentsValue } }),
});
if (node !== null) result.set(call.callSiteId, node);
if (node !== null) {
result.set(call.callSiteId, node);
if (transferOmitted > 0) {
const detail = `${String(transferOmitted)} direct call arguments exceeded the ${String(JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES)}-byte worker transfer record ceiling.`;
addSemanticGraphUnknown(state, {
node_id: node.node_id,
family: "call-flow",
relation_kinds: ["argument-to-parameter"],
reason: "resource-limit",
detail,
candidate_node_ids: [],
evidence: {
...unknownSemanticEvidence(file, call.location),
coverage: partialApplicationCoverage(
javaScriptAnalysisCoverageLimits({
transfer_record_bytes:
JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES,
}),
null,
),
limitations: [detail],
},
});
}
if (
call.arguments.some(
(argument) => argument.literalResourceLimit === "primitive-bytes",
)
) {
const limit = "primitive-bytes";
addSemanticGraphUnknown(state, {
node_id: node.node_id,
family: "call-flow",
relation_kinds: ["argument-to-parameter"],
reason: "resource-limit",
detail: semanticResourceLimitReason(limit),
candidate_node_ids: [],
evidence: {
...unknownSemanticEvidence(file, call.location),
coverage: partialApplicationCoverage(
semanticResourceLimitCoverage([limit]),
null,
),
limitations: [semanticResourceLimitReason(limit)],
},
});
}
}
}
return result;
}