Skip to content

feat(callgraph): C++ call graph builder#675

Merged
shivasurya merged 1 commit intomainfrom
shiva/cpp-cpp-call-graph
May 3, 2026
Merged

feat(callgraph): C++ call graph builder#675
shivasurya merged 1 commit intomainfrom
shiva/cpp-cpp-call-graph

Conversation

@shivasurya
Copy link
Copy Markdown
Owner

Summary

Adds BuildCppCallGraph — the four-pass C builder plus three C++-specific resolution paths exercised in Pass 4. Plain free-function calls fall through to resolveCCallTarget, making the C++ builder a strict superset of the C one.

Step Resolves
1 Qualified call (ns::func, Class::staticMethod) — direct NamespaceIndex lookup
2 this->method() — caller's enclosing class derived via byte-range containment
3 Method on typed receiver — receiver type from the type engine, method on that class
4 C-style fallthrough — definition-preferring lookup shared with the C builder

Receiver type normalisation

obj.method() calls drive resolution through the receiver's declared type. Before the lookup, the type string is normalised: const, volatile, and pointer/reference suffixes (*, **, &, &&) are stripped so Dog*, const Dog&, and Dog ** all reduce to Dog.

Class member tracking (Pass 2)

  • Class method return types registered on the type engine for both definitions and declarations — header-only inline declarations still seed receiver-typed resolution.
  • Field types extracted from field_declaration nodes for future field-chain resolution (obj.field.method()).

Method-to-class association

Same byte-range containment used in PR-05 (BuildCppModuleRegistry): for each method, find the smallest class declaration whose [StartByte, EndByte) range contains the method's start byte. Nested classes (class Outer { class Inner { ... }; };) resolve to the innermost match. The builder doesn't depend on parser-internal context tracking, so it stays composable across future parser refactors.

Test plan

  • go build ./...
  • go test ./... — full suite green
  • go vet ./...
  • golangci-lint run ./graph/callgraph/builder/ — 0 issues
  • Coverage on cpp_builder.go lines: ~93%
  • Spec scenarios covered:
    • Namespace-qualified call (mylib::process)
    • Static method via NamespaceIndex (Socket::create)
    • Method on typed receiver (dog.speak() where dog: Dog*)
    • this->method() inside a method body
    • Free-function fallthrough
    • printf (stdlib) → unresolved with failure reason
    • Receiver not in scope → unresolved (no panic)
    • Method return type registered on the type engine; void dropped
    • Field types registered on the type engine
    • Receiver normalisation across pointer/reference/const/volatile/whitespace shapes
    • Nested classes pick the innermost match
    • Suffix match on namespaced classes (Foons::Foo::bar)
    • Merges cleanly into a unified graph
    • Receiver binding with nil Type doesn't panic
    • this->method() on a non-method caller falls through cleanly
    • Header-only declarations still register class method return types
    • Non-C++ nodes ignored (mixed-language safety)

Stacked on

shiva/cpp-c-call-graph (#674)

@shivasurya shivasurya added enhancement New feature or request go Pull requests that update go code labels May 2, 2026
@shivasurya shivasurya self-assigned this May 2, 2026
@safedep
Copy link
Copy Markdown

safedep Bot commented May 2, 2026

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

No dependency changes detected. Nothing to scan.

View complete scan results →

This report is generated by SafeDep Github App

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Code Pathfinder Security Scan

Pass Critical High Medium Low Info

No security issues detected.

Metric Value
Files Scanned 2
Rules 205

Powered by Code Pathfinder

@codecov
Copy link
Copy Markdown

codecov Bot commented May 2, 2026

Codecov Report

❌ Patch coverage is 89.80583% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.33%. Comparing base (897e99d) to head (2aef962).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
sast-engine/graph/callgraph/builder/cpp_builder.go 89.80% 11 Missing and 10 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #675      +/-   ##
==========================================
+ Coverage   85.30%   85.33%   +0.02%     
==========================================
  Files         183      184       +1     
  Lines       26545    26751     +206     
==========================================
+ Hits        22644    22827     +183     
- Misses       3034     3045      +11     
- Partials      867      879      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Owner Author

shivasurya commented May 3, 2026

Merge activity

  • May 3, 1:15 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 3, 1:29 PM UTC: Graphite rebased this pull request as part of a merge.
  • May 3, 1:30 PM UTC: @shivasurya merged this pull request with Graphite.

@shivasurya shivasurya changed the base branch from shiva/cpp-c-call-graph to graphite-base/675 May 3, 2026 13:27
@shivasurya shivasurya changed the base branch from graphite-base/675 to main May 3, 2026 13:28
Add BuildCppCallGraph — same four-pass structure as the C builder
plus three C++-specific resolution paths:

  1. Namespace-/scope-qualified calls (`ns::func`,
     `Class::staticMethod`) resolve directly through
     registry.NamespaceIndex.
  2. Method calls on typed receivers (`obj.method()` /
     `obj->method()`) look up the receiver's declared type via the
     type engine and then locate the method on that class. The
     receiver type is normalised: pointer/reference qualifiers,
     `const`, `volatile`, and trailing whitespace are stripped before
     class lookup so `Dog*`, `const Dog&`, and `Dog **` all reduce to
     `Dog`.
  3. `this->method()` derives the receiver type from the caller's
     enclosing class via byte-range containment, so calls inside
     method bodies resolve without a separate `this` symbol in the
     scope.

Plain free-function calls fall through to resolveCCallTarget so the
C++ builder is a strict superset of the C one. Pass 2 also registers
class method return types and class field types on the type engine
to support future receiver-typed resolution chains.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@shivasurya shivasurya force-pushed the shiva/cpp-cpp-call-graph branch from 08fdce6 to 2aef962 Compare May 3, 2026 13:29
@shivasurya shivasurya merged commit 7814406 into main May 3, 2026
6 checks passed
@shivasurya shivasurya deleted the shiva/cpp-cpp-call-graph branch May 3, 2026 13:30
shivasurya added a commit that referenced this pull request May 3, 2026
## Summary

Adds `ExtractCStatements` and `ExtractCppStatements` — they walk a parsed function body and emit one `*core.Statement` per recognised construct (declaration, assignment, call, return, if/for/while/do/switch, plus throw / try / range-for in C++). The result feeds the CFG builder (PR-10) and the future variable-dependency graph.

### Statement shapes captured

| Construct | Statement |
|---|---|
| `int x = a + b;` | `{Type: assignment, Def: x, Uses: [a, b]}` |
| `x = func(y);` | `{Type: assignment, Def: x, Uses: [y], CallTarget: func}` |
| `func(a, b);` | `{Type: call, CallTarget: func, Uses: [a, b]}` |
| `return x;` | `{Type: return, Uses: [x]}` |
| `if (x>0) { ... } else { ... }` | `{Type: if, Uses: [x], NestedStatements, ElseBranch}` |
| `for (int i=0; i<n; i++)` | `{Type: for, Def: i, Uses: [n]}` |
| `while (cond) { ... }` | `{Type: while, Uses: [cond]}` |
| `p->name = val;` | `{Type: assignment, Def: p, Uses: [val]}` |
| `buf[i] = input[j];` | `{Type: assignment, Def: buf, Uses: [input, i, j]}` |
| `obj.method(x);` | `{Type: call, CallTarget: method, CallChain: obj.method, Uses: [obj, x]}` |
| `ns::sort(begin, end);` | `{Type: call, CallTarget: ns::sort, Uses: [begin, end]}` |
| `auto x = obj.get();` | `{Type: assignment, Def: x, Uses: [obj], CallTarget: get}` |
| `throw std::runtime_error(msg);` | `{Type: raise, CallTarget: std::runtime_error}` |
| `for (auto x : items)` | `{Type: for, Def: x, Uses: [items]}` |

### Design notes

- **One shared dispatcher**: `clikeExtractor` in `statements_clike.go` owns every node-type handler. C and C++ are thin wrappers that bind a keyword predicate (`clike.IsCKeyword` / `clike.IsCppKeyword`) and an `extraNodeHandler` hook for C++-only nodes. Adding the next clike construct touches one file.
- **LHS collapsing**: `buf[i] = ...`, `p->name = ...`, `(*p) = ...` all collapse to the base variable for `Def`; index/field expressions surface as additional `Uses`. Matches the def-use convention used by the Go and Python extractors.
- **Loop-variable hygiene**: the C-style and range-based `for` handlers add the defined loop variable to `Def` and remove it from `Uses` last, so the variable appears once even though it shows up in init / cond / update.
- **Identifier walker** filters `field_identifier` (the method name in `obj.method`), `type_identifier`, `qualified_identifier` (treated atomically as a call target rather than as variable uses), and every literal type — keeping `Uses` to true variable references.
- **Catch clauses** are flattened: the caught variable becomes the `Def` of an empty assignment statement so def-use sees the binding site, then the body's statements follow in `ElseBranch`.

## Test plan

- [x] `go build ./...`
- [x] `go test ./...` — full suite green
- [x] `go vet ./...`
- [x] `golangci-lint run ./graph/callgraph/extraction/` — 0 issues
- [x] Coverage on new lines: 88.2%
- [x] Spec C scenarios covered: assignment with binary op, assignment from call, bare call, if/else, for loop (with loop-var dropped from Uses), while, do-while, switch, pointer-arrow assignment, subscript assignment, keyword filter (`sizeof`, `(int)`, `NULL`), bare declaration, forward declaration (nil function).
- [x] Spec C++ scenarios covered: method call on object, qualified call, auto-from-method-call, throw constructor, try/catch (with bound exception name as Def), range-based for, C++ keyword filter (`nullptr`, `static_cast`, `delete`, `this`, `auto`), C-style fallthrough, namespace assignment.

## Stacked on

`shiva/cpp-cpp-call-graph` (#675)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant