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.

repowise capabilities for C++
Files parsed.cpp, .cc, .cxx, .h, .hh, .hpp, .hxx, plus include fragments such as .inl and .tpp
Import resolutionFull. compile_commands.json include dirs per file, public-header map, per-target include roots, importer-relative paths; system headers dropped
Call graphResolved calls, inheritance and type-use edges
Dead codeUnreachable files and unused symbols; preprocessor-aware; public headers live when referenced
Health markersComplexity, class metrics, Extract Method plans
Performance markersNarrow by design: sqlite3_step, fopen and socket calls inside range-for loops, std::regex built per iteration
Coverage reportslcov from gcov or llvm-cov, Cobertura from gcovr
Frameworks recognisedGoogleTest, 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

Native C++ analysis tools
ToolWhat it covers
Compiler warnings (-Wunused-function and family)Unused static functions and variables per translation unit. Exact, free, on every build.
clang-tidymisc-unused-* checks and readability-function-cognitive-complexity, driven by compile_commands.json.
cppcheckunusedFunction across the files it is given, plus many bug checks.
Doxygen with GraphvizCall and caller graphs per function in generated HTML docs.
include-what-you-useUnneeded 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 dashboard

Questions

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.

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.