Normalize call-graph edge representation across language backends - #205
Open
tanbing117 wants to merge 6 commits into
Open
Normalize call-graph edge representation across language backends#205tanbing117 wants to merge 6 commits into
tanbing117 wants to merge 6 commits into
Conversation
Introduce a unified call-edge contract so that both FM-Agent's main
pipeline and downstream analysis plugins consume the same precise call
graph.
## What changed
* codegraph.py: get_call_edges() now returns a list of edge dicts
[{caller, callee, kind, span}] instead of {caller: {callee}}. The
edge kind (calls vs constructor) is preserved, and call-site location
(start_line/start_column) is attached. Results are ORDER BY source
location so call-site order aligns with source appearance order.
* registry.py: normalize_call_edges() unifies dict (incl. single-string
callee), list, and custom-object edge formats into one schema, with
schema validation (skip malformed edges with a warning), an allowlist
for custom-object fields, and language passthrough.
* generate_topdown_layers.py: adapt the main pipeline's consumer to the
new list format by converting back to the legacy {caller: {callee}}
shape at the boundary, preserving existing behavior.
## Motivation
Security analysis plugins (IFC, taint, crypto, resource) need precise
call graphs — distinguishing same-named functions, preserving edge
kind, and carrying call-site argument bindings — which the old
FQN-set output could not provide. This change exposes those details
through a single normalized contract, to be consumed by plugins via a
ProgramIndex builder.
- dedup key includes file/line/col so multiple call sites of the same callee within one function are all preserved (previously the 2nd and later calls were dropped, breaking order_index and arg_bindings) - span now carries the caller source file path for cross-file debugging and finding localization
…t extraction
- normalize_call_edges: dedup at call-site granularity (idempotent)
- call_edges_all: dedup across language backends
- normalize span field names to {file, start_line, start_column}
(accepts path/line/col aliases)
- empty language string is overwritten by the caller language
- custom edge objects extracted via getattr (works with __slots__
and @Property, not just __dict__)
- generate_topdown_layers: defensive .get() and defaultdict
Edges from different backends (e.g. C and C++) with the same caller/callee/span must not be merged away by cross-language dedup.
The _append closure previously referenced out defined per-branch; hoist out and seen to the function scope so the closure is unambiguous.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a9469dec0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
get_call_edges() previously used the caller function node's definition location (s.start_line/s.start_column) as the span. Multiple call sites of the same callee therefore collapsed to the same span, breaking dedup and call-site ordering. - calls: span now uses e.line/e.col (the precise call-site location); caller node still provides the source file identity - constructor: COALESCE(e.line, s.start_line) / COALESCE(e.col, s.start_column) so instantiates edges lacking coordinates fall back to the caller node location
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#204
Motivation
FM-Agent is integrating security analysis plugins (IFC, taint, crypto, resource) that perform cross-function data-flow analysis and need a precise call graph — distinguishing same-named functions, preserving edge kind, and carrying call-site location. The current
get_call_edges()output (FQN-set dict) cannot provide these.What changed
src/languages/codegraph.py
get_call_edges()returns a list of edge dicts[{caller, callee, kind, span}]instead of{caller: {callee}}.callsvsconstructor) preserved.{file, start_line, start_column}.ORDER BYsource location so call-site order aligns with source appearance.src/languages/registry.py
normalize_call_edges()unifies dict (incl. single-string callee), list, and custom-object edge formats into one schema.{file, start_line, start_column}.__slots__/@propertycompatible).src/generate_topdown_layers.py
{caller: {callee}}shape at the boundary, preserving existing behavior exactly.Design
codegraph → list{caller, callee, kind, span}
→ registry.normalize_call_edges()
→ main pipeline and → ProgramIndex for plugins
This is the first step toward a shared ProgramIndex contract; edge kind and call-site metadata are preserved end-to-end for future plugin consumption.