Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad3d995604 |
No files matched your search
@@ -94,6 +94,37 @@ it.each([
|
||||
},
|
||||
);
|
||||
|
||||
it.each([false, true])(
|
||||
"traces a directly returned member read back to its literal (candidates: %s)",
|
||||
(candidates) => {
|
||||
const graph = graphFor(`
|
||||
const settings = { index: "products" };
|
||||
function viaLocal() { const value = settings.index; return value; }
|
||||
function viaMember() { return settings.index; }
|
||||
const a = viaLocal();
|
||||
const b = viaMember();
|
||||
`);
|
||||
for (const label of ["a", "b"]) {
|
||||
const binding = graph.nodes.find(
|
||||
(node) => node.kind === "binding" && node.label === label,
|
||||
);
|
||||
if (binding === undefined) throw new Error(`Expected binding ${label}`);
|
||||
const trace = queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "semantic-node", node_id: binding.node_id },
|
||||
direction: "backward-provenance",
|
||||
include_ambiguous_dynamic_edges: candidates,
|
||||
});
|
||||
expect(trace.status, label).toBe("found");
|
||||
expect(
|
||||
trace.nodes.filter(({ kind }) => kind === "literal"),
|
||||
label,
|
||||
).toEqual([
|
||||
expect.objectContaining({ properties: { value: "products" } }),
|
||||
]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("projects closure and direct interprocedural flow without execution", () => {
|
||||
const graph = graphFor(`
|
||||
const outer = 2;
|
||||
|
||||
@@ -197,6 +197,14 @@ export const projectSemanticReturnValues = (
|
||||
for (const callable of context.ir.callables)
|
||||
for (const site of callable.returnSites) {
|
||||
const returnNode = context.returnSiteNodes.get(site.returnSiteId);
|
||||
// A non-identifier return evaluated to one exact primitive is defined by
|
||||
// its literal (see projectSemanticValues); references inside it, such as
|
||||
// a member read's receiver, are not candidates for the returned value.
|
||||
if (
|
||||
site.identityReferenceLocation === null &&
|
||||
site.value.status === "literal"
|
||||
)
|
||||
continue;
|
||||
const references = context.referenceNodesWithin(site.location);
|
||||
for (const reference of references)
|
||||
addSemanticGraphRelation(context.state, {
|
||||
|
||||
@@ -34,6 +34,34 @@ export const projectSemanticValues = (
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
// A return through an identifier already reaches its value through the
|
||||
// resolved binding alias. Any other returned expression, such as a member
|
||||
// read, carries its evaluated primitive on the return site itself.
|
||||
for (const callable of context.ir.callables)
|
||||
for (const site of callable.returnSites) {
|
||||
const returnNode = context.returnSiteNodes.get(site.returnSiteId);
|
||||
if (returnNode === undefined || site.identityReferenceLocation !== null)
|
||||
continue;
|
||||
const primitives =
|
||||
site.value.status === "literal"
|
||||
? [site.value.value]
|
||||
: site.value.status === "union"
|
||||
? site.value.values
|
||||
: [];
|
||||
for (const primitive of primitives)
|
||||
addSemanticGraphRelation(context.state, {
|
||||
source: addLiteralNode(context, {
|
||||
key: `return:${site.returnSiteId}`,
|
||||
location: site.location,
|
||||
functionNodeId: returnNode.function_node_id,
|
||||
value: primitive,
|
||||
}),
|
||||
target: returnNode,
|
||||
relation: "defines",
|
||||
resolution:
|
||||
site.value.status === "literal" ? "resolved" : "candidate",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
interface ValueProjectionInput {
|
||||
@@ -50,7 +78,10 @@ const projectValue = (input: ValueProjectionInput): void => {
|
||||
path.length === 0 ? "binding" : `property:${semanticPropertyPointer(path)}`;
|
||||
if (target === null) return;
|
||||
if (value.status === "literal") {
|
||||
const literal = addLiteralNode(context, binding, value.value, role);
|
||||
const literal = addLiteralNode(
|
||||
context,
|
||||
bindingLiteral(context, binding, value.value, role),
|
||||
);
|
||||
addSemanticGraphRelation(context.state, {
|
||||
source: literal,
|
||||
target,
|
||||
@@ -59,7 +90,10 @@ const projectValue = (input: ValueProjectionInput): void => {
|
||||
});
|
||||
} else if (value.status === "union")
|
||||
for (const primitive of value.values) {
|
||||
const literal = addLiteralNode(context, binding, primitive, role);
|
||||
const literal = addLiteralNode(
|
||||
context,
|
||||
bindingLiteral(context, binding, primitive, role),
|
||||
);
|
||||
addSemanticGraphRelation(context.state, {
|
||||
source: literal,
|
||||
target,
|
||||
@@ -134,11 +168,32 @@ const projectValue = (input: ValueProjectionInput): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const addLiteralNode = (
|
||||
interface LiteralOwner {
|
||||
/** Stable owner and role, such as a binding slot or a return site. */
|
||||
readonly key: string;
|
||||
readonly location:
|
||||
| JavaScriptSemanticBinding["definitions"][number]["location"]
|
||||
| null;
|
||||
readonly functionNodeId: string | null;
|
||||
readonly value: string | number | boolean | null;
|
||||
}
|
||||
|
||||
const bindingLiteral = (
|
||||
context: SemanticFlowProjectionContext,
|
||||
binding: JavaScriptSemanticBinding,
|
||||
value: string | number | boolean | null,
|
||||
role: string,
|
||||
): LiteralOwner => ({
|
||||
key: `${binding.bindingId}:${role}`,
|
||||
location: binding.definitions[0]?.location ?? null,
|
||||
functionNodeId:
|
||||
context.bindingNodes.get(binding.bindingId)?.function_node_id ?? null,
|
||||
value,
|
||||
});
|
||||
|
||||
const addLiteralNode = (
|
||||
context: SemanticFlowProjectionContext,
|
||||
{ key, location, functionNodeId, value }: LiteralOwner,
|
||||
) => {
|
||||
const encodedValue = JSON.stringify(value);
|
||||
const valueDigest = `value-sha256:${digestCanonicalValue(value, "Semantic literal")}`;
|
||||
@@ -149,14 +204,13 @@ const addLiteralNode = (
|
||||
kind: "literal",
|
||||
// Identity needs an exact value commitment, not another payload copy.
|
||||
// Keep the complete literal in properties for queries and Evidence export.
|
||||
roleKey: `literal:${binding.bindingId}:${role}:${valueKey}`,
|
||||
location: binding.definitions[0]?.location ?? null,
|
||||
roleKey: `literal:${key}:${valueKey}`,
|
||||
location,
|
||||
label:
|
||||
typeof value === "string" && encodedValue.length > stringLabel.length
|
||||
? stringLabel
|
||||
: encodedValue,
|
||||
functionNodeId:
|
||||
context.bindingNodes.get(binding.bindingId)?.function_node_id ?? null,
|
||||
functionNodeId,
|
||||
properties: { value },
|
||||
});
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user