Languages / C++
C++ code analysis: call graphs, dead code and complexity
To get a call graph and dead code for a C++ codebase without wiring up a compiler plugin, run repowise init. It resolves #include through compile_commands.json when the repository has one, treats main, test macros and fuzz targets as entry points, and skips symbols reached through #define aliases or token pasting, so macro-heavy code is not reported as dead.
Last reviewed: November 2026
Full tier
What repowise detects in C++
Checked against the engine source. Every row runs locally with zero LLM calls.
| Files parsed | .cpp, .cc, .cxx, .h, .hh, .hpp, .hxx, plus include fragments such as .inl and .tpp |
|---|---|
| Import resolution | Full. compile_commands.json include dirs per file, public-header map, per-target include roots, importer-relative paths; system headers dropped |
| Call graph | Resolved calls, inheritance and type-use edges |
| Dead code | Unreachable files and unused symbols; preprocessor-aware; public headers live when referenced |
| Health markers | Complexity, class metrics, Extract Method plans |
| Performance markers | Narrow by design: sqlite3_step, fopen and socket calls inside range-for loops, std::regex built per iteration |
| Coverage reports | lcov from gcov or llvm-cov, Cobertura from gcovr |
| Frameworks recognised | GoogleTest, Catch2, Boost.Test, doctest, Google Benchmark, libFuzzer |
Specific to this language
How C++ code is read
compile_commands.json, when you have it
repowise looks for compile_commands.json at the repository root and in build/. When present, each file's own include directories resolve its #include lines, which is what makes a C++ graph accurate. Without it, resolution falls back to a public-header map, per-target include roots and importer-relative paths, and dead code leans on type-use edges.
The preprocessor is not ignored
C++ dead code tracks #if, #ifdef, #ifndef and #elif branches and push_macro or pop_macro. A symbol reached through a #define alias, ## token pasting or a typedef tag is not flagged, and CMake option() alternative sources are treated as live.
Test and fuzz targets are roots
Files with main or LLVMFuzzerTestOneInput are entry points. GoogleTest TEST_F, Catch2 TEST_CASE_METHOD and BOOST_FIXTURE_TEST_CASE link a test to the file that defines its fixture, and test and benchmark files are roots.
Honest comparison
Native C++ tools, and when they are enough
| Tool | What it covers |
|---|---|
| Compiler warnings (-Wunused-function and family) | Unused static functions and variables per translation unit. Exact, free, on every build. |
| clang-tidy | misc-unused-* checks and readability-function-cognitive-complexity, driven by compile_commands.json. |
| cppcheck | unusedFunction across the files it is given, plus many bug checks. |
| Doxygen with Graphviz | Call and caller graphs per function in generated HTML docs. |
| include-what-you-use | Unneeded and missing #include directives. |
Where repowise adds value
- Cross-target dead code: public functions and whole files nothing in the repository reaches, with a confidence and owner.
- A call graph you can query from an agent (get_context, get_answer) or browse in the dashboard, not static images.
- Hotspots, ownership and co-change from git history, which matter most in old C++ code with few tests.
- One graph with the Python bindings, Rust or Go code beside the C++.
How to run it
Analyse a C++ repository
From the repository root. Agents get the same results through the get_dead_code, get_health and get_context MCP tools.
uv tool install repowise # or: pipx install repowise
repowise init --yes --no-prose # index the repo, no API key
repowise dead-code --min-confidence 0.7 --safe-only
repowise health --refactoring-targets
lcov --capture --directory build --output-file coverage.info
repowise coverage add coverage.info
repowise serve # graph, health and dead code in a local dashboardQuestions
What people ask before they try it.
How do I generate a C++ call graph?
Export compile_commands.json from your build (CMake: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON), then run repowise init. Every call edge is recorded with its resolution origin and confidence. Browse it with repowise serve or ask an agent through get_context with include=["callers"].
Does it need to compile my code?
No. repowise parses with tree-sitter. compile_commands.json is read for include paths only; no compiler runs.
How does it handle macros?
Conservatively. Symbols reached through #define aliases, ## token pasting or typedef tags are not flagged as dead, and conditional-compile alternatives are treated as live, so the report errs toward keeping code.
Is plain C supported?
Yes, in the Good tier: symbols, imports, a call graph, dead code and complexity.
Does repowise send my C++ code to an LLM?
No. Parsing, the call graph, dead code, health scores, performance markers and git history all run locally with zero LLM calls. repowise init --yes --no-prose needs no API key, and the analysis itself makes no network calls. Written documentation prose is the one opt-in step that uses a model, and it can run on a local Ollama model.
Related reading
Index a C++ repository.
Public repositories are free on repowise.dev. The CLI is open source and runs on your machine with no API key.