Skip to content

Normalize call-graph edge representation across language backends - #205

Open
tanbing117 wants to merge 6 commits into
fmagent-project:mainfrom
tanbing117:feat/callgraph-edge-contract
Open

Normalize call-graph edge representation across language backends#205
tanbing117 wants to merge 6 commits into
fmagent-project:mainfrom
tanbing117:feat/callgraph-edge-contract

Conversation

@tanbing117

Copy link
Copy Markdown
Contributor

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}}.
  • Edge kind (calls vs constructor) preserved.
  • Each edge carries call-site location {file, start_line, start_column}.
  • Results ORDER BY source location so call-site order aligns with source appearance.
  • Dedup key includes file/line/col so multiple call sites of the same callee are all preserved.

src/languages/registry.py

  • normalize_call_edges() unifies dict (incl. single-string callee), list, and custom-object edge formats into one schema.
  • Schema validation skips malformed edges with a warning (shared infrastructure, never silent).
  • Dedup at call-site granularity (idempotent); includes language so C/C++ backends don't merge away.
  • Span field-name aliases normalized to {file, start_line, start_column}.
  • Empty language overwritten by caller language; custom objects extracted via getattr (__slots__/@property compatible).

src/generate_topdown_layers.py

  • Adapts the main pipeline consumer to the new list format by converting back to the legacy {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.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/languages/codegraph.py Outdated
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant