Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
124 lines
3.4 KiB
JavaScript
124 lines
3.4 KiB
JavaScript
import { freezeFacts as freeze } from "./fact-reads.mjs";
|
|
import { nativePage } from "./native-reads.mjs";
|
|
|
|
function search(outgoing, from, target, avoid) {
|
|
const blocked = new Set(avoid);
|
|
if (blocked.has(from) || (target.kind === "block" && blocked.has(target.id)))
|
|
return {
|
|
status: "not-found",
|
|
end: null,
|
|
items: [],
|
|
examined_blocks: 0,
|
|
examined_edges: 0,
|
|
search: "excluded-endpoint",
|
|
};
|
|
const queue = [from],
|
|
predecessor = new Map([[from, null]]);
|
|
let examined_blocks = 0,
|
|
examined_edges = 0;
|
|
for (let cursor = 0; cursor < queue.length; cursor++) {
|
|
const current = queue[cursor],
|
|
edges = outgoing.get(current) ?? [];
|
|
examined_blocks++;
|
|
if (target.kind === "block" ? current === target.id : edges.length === 0) {
|
|
const path = [];
|
|
for (let at = current; predecessor.get(at) !== null;) {
|
|
const edge = predecessor.get(at);
|
|
path.push(edge);
|
|
at = edge.from;
|
|
}
|
|
path.reverse();
|
|
return {
|
|
status: "found",
|
|
end: current,
|
|
items: path.map((edge, step_index) => freeze({ ...edge, step_index })),
|
|
examined_blocks,
|
|
examined_edges,
|
|
search: "shortest-witness",
|
|
};
|
|
}
|
|
for (const edge of edges) {
|
|
examined_edges++;
|
|
if (!blocked.has(edge.to) && !predecessor.has(edge.to)) {
|
|
predecessor.set(edge.to, edge);
|
|
queue.push(edge.to);
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
status: "not-found",
|
|
end: null,
|
|
items: [],
|
|
examined_blocks,
|
|
examined_edges,
|
|
search: "reachable-subgraph-exhausted",
|
|
};
|
|
}
|
|
|
|
// Consume validated identities/edges, never provider text or machine predicates.
|
|
// A resident immutable graph owns at most one cached query and O(V) search state.
|
|
export function createGraphPathReader(blocks, outgoing) {
|
|
let last;
|
|
return (input) => {
|
|
const avoid = [...new Set(input.avoid ?? [])];
|
|
const selected = [
|
|
input.from,
|
|
...(input.target.kind === "block" ? [input.target.id] : []),
|
|
...avoid,
|
|
];
|
|
const unretained_blocks = [...new Set(selected)].filter(
|
|
(id) => !blocks.has(id),
|
|
);
|
|
let result;
|
|
if (unretained_blocks.length) {
|
|
result = {
|
|
status: "unknown",
|
|
end: null,
|
|
items: [],
|
|
examined_blocks: 0,
|
|
examined_edges: 0,
|
|
search: "unretained-selection",
|
|
};
|
|
} else {
|
|
const key = JSON.stringify([
|
|
input.from,
|
|
input.target.kind,
|
|
input.target.kind === "block" ? input.target.id : null,
|
|
[...avoid].sort((a, b) => a - b),
|
|
]);
|
|
if (last?.key !== key)
|
|
last = {
|
|
key,
|
|
result: freeze(search(outgoing, input.from, input.target, avoid)),
|
|
};
|
|
result = last.result;
|
|
}
|
|
const {
|
|
items,
|
|
examined_blocks,
|
|
examined_edges,
|
|
search: scope,
|
|
...outcome
|
|
} = result;
|
|
return {
|
|
...outcome,
|
|
from: input.from,
|
|
target: { ...input.target },
|
|
avoid,
|
|
...nativePage(items, input),
|
|
total: outcome.status === "unknown" ? null : items.length,
|
|
unretained_blocks,
|
|
path_coverage: {
|
|
scope:
|
|
"recovered CFG under requested exclusions; one shortest edge-count witness in native edge order",
|
|
search: scope,
|
|
examined_blocks,
|
|
examined_edges,
|
|
branch_predicate_feasibility: "unknown",
|
|
machine_semantic_equivalence: "unknown",
|
|
runtime_reachability: "unknown",
|
|
},
|
|
};
|
|
};
|
|
}
|