complete-callgraph ยท diff

git:20261006.be8b282 to git:20261006.9cb37da

0 added, 3 removed. Audit A to A.

---
name: complete-callgraph
description: Resolve unresolved indirect calls (function pointers, callback structs) into a labeled call-graph overlay with rllvm-query. Use when reach/closure/callers miss paths through callbacks, or when asked to complete the call graph.
---
# Completing the call graph
rllvm-query cannot bound a call through a function pointer, so `reach`,
`closure` and `callers` miss paths that run through callbacks. You can read
the code and record the missing edges in an overlay, a file beside the
catalog. Every edge is a hypothesis you label with confidence and the source
lines behind it; the catalog never changes and no answer uses the overlay
unless asked. Load the program first (`load_catalog`, see the `query` skill).
## When
`uncertainty.indirect_call_sites` is non-zero and an answer depends on a
callback path: a `reach` found no path, or a path you need may go through a
function table, an ops struct or a registered handler. Without that
dependence, do not start; completing a whole program is rarely the goal.
## The loop
1. `load_catalog`, then `resolution_candidates`. It groups unresolved
indirect sites by the record field they dispatch through, with `field`
(`ops@8`: record and byte offset), `field_name`, `signature`, `sites`, and
`candidates`: functions stored into that field, each with its
`assignments` and whether `signature_matches`.
2. For each group, read the source of every assignment: `at` (with the
assignment's `file` and `line`) shows what runs at that line; read the
file itself for the source. Confirm the member is `field_name` and the
struct is the record type, not a same-named field of another struct.
`uses` on a candidate lists every place its address is taken.
3. Decide the targets. A candidate is not a target until you have read the
assignment. `single_candidate` is a hint to check, not an answer.
4. `record_edges` with `add` records (below): `via_field`, `to`, `confidence`
and `provenance`, quoting each assignment as `file:line: text`.
5. `list_overlay` to see the edges and the sites they cover, then
`save_overlay`. Records stay in memory until it runs.
6. Ask the question again with `include_overlay` on `reach`, `closure` or
`slice`.
- The user can watch progress with `/overlay-view`, a read-only pane of each
- unresolved field, its candidates and the edges recorded so far.
-
## Confidence
- `high`: the assignment stores this function into this field of this record
type, and the signature matches.
- `medium`: one indirection away (a helper copies the table into the record)
or the signature differs by a cast.
- `low`: a match by name or signature only.
`min_confidence` on a walk excludes edges below it.
## Disciplines
- Patterns, not sites: when a group has a `field`, record with `via_field` so
the edge covers every unresolved site through that field. Use `site` only
for a site with no field.
- Never record without provenance. It is required, and it is what a reader
checks.
- Never call an agent edge proven. When reporting a path that uses one, say
"not proven" and show each agent step with its `provenance` quoted.
- Edges attach only to unresolved indirect sites. An edge on a direct or
bounded site is rejected, and a batch with one bad record applies none; the
error names its index.
- `to` is an exact symbol naming one definition. For a `static` function
defined in several modules, give `{"module_id", "symbol"}`; the error lists
the candidates' module ids.
- A verdict records whether the edge exists. It persists when a later `add`
updates confidence or provenance. To reset it, `retract` and then `add`
again.
## Reading a path with the overlay
`uncertainty.agent_path_steps` counts agent steps; non-zero means the path is
not proven. Then rerun the same query without `include_overlay` to look for a
proven path. A breadth-first search can return a shorter or equal-length path
through agent edges even when a direct path exists, and reporting that one
under-claims. Report the proven path if there is one, else the agent path as
a hypothesis. `closure` lists functions reached only through agent edges in
`uncertainty.agent_reached`, and `uncertainty.overlay` gives `covered_sites`
of `unresolved_sites`: say how many sites remain unresolved.
## Saving
`save_overlay` is the only tool that writes. Save before `unload_catalog`,
before reloading, and before the session ends; unsaved records are not
written on exit. `load_overlay` refuses to replace one holding unsaved
records unless `discard_pending` is true. After a rebuild the overlay refuses
to load, naming both fingerprints: move or delete the file, or name another
with `--overlay` (MCP: `path`), and record again.
## Verification
For an edge a conclusion depends on, do not rely on the first reading:
1. `slice` from the caller to the target with `include_overlay` and
`emit_module`, which writes the slice's definitions as one small `.bc`.
2. Re-read that module carefully, or run a scoped points-to analysis on it.
3. Record the result with a `verify` record: `tool` is `reread`, `svf` or
`phasar`, `verdict` is `confirmed`, `refuted` or `inconclusive`. A refuted
edge is skipped by every walk.
A `confirmed` verdict from `reread` is still your judgment, not proof.
## Records
`record_edges` takes these as `records`; `rllvm-query overlay record` takes
them as JSON lines on stdin.
The `edge` of a `verify` or `retract` is the `key` object of an edge in
`list_overlay`, either `{via_field, to}` or `{site, to}`; copy it verbatim.
```json
{"op":"add","via_field":{"record":"ops","offset":8},"to":"handler","confidence":"high","provenance":["ops.c:8: o->cb = handler"]}
{"op":"verify","edge":{"via_field":{"record":"ops","offset":8},"to":{"module_id":"<id>","symbol":"handler"}},"tool":"reread","verdict":"confirmed"}
{"op":"retract","edge":{"via_field":{"record":"ops","offset":8},"to":{"module_id":"<id>","symbol":"handler"}},"reason":"re-read: the assignment is dead code"}
```