Skip to content

Add callsite-level, source-located call graph export example - #863

Open
WizardBornov wants to merge 2 commits into
secure-software-engineering:developmentfrom
WizardBornov:add-callsite-level-cg-export
Open

WizardBornov wants to merge 2 commits into
secure-software-engineering:developmentfrom
WizardBornov:add-callsite-level-cg-export

Conversation

@WizardBornov

Copy link
Copy Markdown

Summary

Adds a standalone example (examples/how-to/09-export-callsite-cg/) that exports PhASAR's call graph as callsite-level, source-located CSV. This came out of a thread with @fabianbs96 about baseline call-graph findings for an FSE 2027 paper — he offered to take this if it looked useful:

You are welcome to create a PR for this tool; it looks very good already. I would then replace the C-style IO, command-line parsing, and manual escaping with LLVM utilities.

Two problems it solves, both hit against a real FFmpeg build (~1.2M+ call edges):

  1. Callsite-level, not function-level. CallGraph::printAsDot() / printAsJson() aggregate function-to-function edges. Correlating against runtime instrumentation ground truth needs individual call-site resolution, so this walks getCallsFromWithin / getCalleesOfCallAt directly and emits one row per resolved edge.

  2. Streaming, not buffered. exportICFGAsJson() builds one in-memory nlohmann::json object holding every edge before writing anything to disk. On FFmpeg-scale input this grew unbounded — 11GB resident plus climbing swap, zero bytes written the entire time. This driver writes each edge to disk the moment it's produced and discards it, so memory stays roughly constant regardless of total edge count.

It also documents and works around a source-location bug: getSrcCodeInfoFromIR resolves File and Line through separate helper calls with different fallback branches for instructions lacking direct !dbg metadata, which can pair a File from one resolution path with a Line from an unrelated one. A concrete case from the FFmpeg run: a callsite reported line 2776 inside a 63-line header. The resolveLocation() helper here instead resolves a single DILocation per instruction and reads File/Line/Column off that same object, explicitly marking (rather than silently guessing at) instructions with no direct location at all.

As Fabian's reply anticipated, this is submitted mostly as-is — C-style FILE* I/O, manual CSV escaping, argv parsing. Happy to rework any of it toward LLVM utilities (raw_ostream, cl::opt, etc.) if that's preferred over him doing it during review.

Test plan

  • Builds cleanly via the example's own CMakeLists.txt against an installed PhASAR (same pattern as the other how-to/ examples)
  • Runs against one of the existing llvm-hello-world sample bitcode files and produces a well-formed CSV
  • Reviewer confirms whether getSrcCodeInfoFromIR fix belongs here as documentation-of-workaround, or should instead be filed as a separate bug against that helper

🤖 Generated with Claude Code

PhASAR's existing exportICFGAsJson() aggregates function-to-function
edges and builds one in-memory JSON object before writing anything to
disk. Correlating a static call graph against runtime instrumentation
data needs callsite-level resolution, and on FFmpeg-scale IR
(~1.2M+ edges) the in-memory approach hits an unbounded memory ceiling
before writing a single byte.

This adds a streaming CSV export built on the same
getCallsFromWithin/getCalleesOfCallAt primitives, plus a
resolveLocation() helper that fixes a File/Line pairing bug in
getSrcCodeInfoFromIR (it composes File and Line from separate helper
calls with different fallback branches, which can mismatch a File from
one instruction with a Line from another).

Invited by Fabian Schiebel to submit this as a PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 21, 2026 07:56

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Unresolved moderate findings affect source accuracy, validation, traversal efficiency, error handling, and memory claims.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 4 Medium severity

Open (4)
What changed in this PR

This PR adds a standalone PhASAR how-to example for exporting callsite-level, source-located call-graph edges as CSV.

Changes:

  • Registers and documents the new example.
  • Implements callsite traversal, source-location resolution, and incremental CSV output.
  • Adds a standalone CMake target.
File Reviewed changes Findings
examples/​how-to/​README.md Adds the example to the index. None
examples/​how-to/​09-export-callsite-cg/​README.md Documents building, usage, and CSV output. Nit (1 vote): qualify memory claims because the complete call graph is already materialized.
examples/​how-to/​09-export-callsite-cg/​export_callsite_cg_streaming.cpp Implements traversal, source-location handling, and CSV export. Moderate: preserve full source paths (3 votes); iterate reachable graph functions (3); handle output errors (3); qualify eager graph-construction/streaming claims (1); reject unknown analysis types (2); validate entry points (1); validate the project IR database before ICFG construction and close output on failure (1).
examples/​how-to/​09-export-callsite-cg/​CMakeLists.txt Defines the standalone executable and PhASAR linkage. None

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +62 to +63
return {Loc->getFilename().str(), Loc->getLine(), Loc->getColumn(),
false};
Comment on lines +87 to +89
llvm::errs() << "Unknown call-graph analysis type '" << S
<< "', defaulting to OTF\n";
return CallGraphAnalysisType::OTF;
// exactly the same traversal exportICFGAsJson does internally, just
// writing (and forgetting) each result immediately instead of
// accumulating all of them.
for (const llvm::Function *Fun : ICF.getAllFunctions()) {
Comment on lines +157 to +161
std::fprintf(
Out, "%s,%s,%u,%u,%s,%s,%u,%u,%s\n",
csvField(Fun->getName().str()).c_str(),
csvField(CallerLoc.File).c_str(),
CallerLoc.Line, CallerLoc.Column,
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.

3 participants