Include-What-You-Use (IWYU) Audit¶
Overview¶
Include-What-You-Use (IWYU) is a static analysis tool that ensures every C/C++ source file includes exactly what it uses — no more, no less. It helps reduce compilation times, prevent transitive dependency coupling, and keep the include graph clean.
This project uses IWYU as part of the dependency hygiene strategy (see Architecture for the include-dependency graph).
Installation¶
# Debian/Ubuntu
sudo apt install -y iwyu
# Or via nala
sudo nala install -y iwyu
# Verify
iwyu --version
# Expected: include-what-you-use 0.23 based on clang ...
IWYU requires a Clang-compatible compilation database (compile_commands.json).
The project generates one automatically via CMake in the build/ directory.
Running the Audit¶
Full Project Scan¶
# Run IWYU on all translation units using the compile database
iwyu_tool -p build 2>&1 > /tmp/iwyu_full.txt
# Without forward-declaration suggestions (recommended for C projects)
iwyu_tool -p build -- -Xiwyu --no_fwd_decls 2>&1 > /tmp/iwyu_full.txt
Single File¶
# Analyze one file with its compile flags from the database
iwyu_tool -p build -- -Xiwyu --no_fwd_decls src/app.c 2>&1
Filtering Results¶
# Show only files with suggested changes (skip "has correct #includes")
grep -E "should (add|remove)" /tmp/iwyu_full.txt
# Count total removable includes
grep "^- #include" /tmp/iwyu_full.txt | wc -l
# Extract per-file removal summary (project headers only)
grep -A100 "should remove" /tmp/iwyu_full.txt \
| grep '^- #include "' \
| sort | uniq -c | sort -rn
Interpreting Results¶
IWYU output follows this format for each translation unit:
path/to/file.c should add these lines:
#include <stdint.h> // uint32_t
#include "other.h" // SomeType
path/to/file.c should remove these lines:
- #include <stdio.h> // lines 5-5
- #include "unused.h" // lines 8-8
The full include list for path/to/file.c:
#include "file.h"
#include <stdint.h>
#include "other.h"
Sections¶
| Section | Meaning |
|---|---|
| should add | Headers that the file uses directly but doesn't include |
| should remove | Headers that are included but not directly used |
| full include list | What IWYU thinks the correct set of includes should be |
| has correct #includes | File needs no changes |
Common False Positives¶
IWYU is powerful but imperfect, especially for C projects with macros and conditional compilation. Always verify before applying suggestions:
| Pattern | Why It's a False Positive |
|---|---|
gl_common.h → glad/glad.h |
gl_common.h provides RAII macros (GL_SAFE_DELETE_*, GL_SCOPE_*) beyond just GL types. Replacing it breaks the API contract. |
immintrin.h → emmintrin.h + smmintrin.h |
immintrin.h is the umbrella header for SSE/AVX/F16C. Splitting it misses AVX-256 intrinsics. |
sched.h removed from header |
Header defines struct sched_param used in a struct member — IWYU sometimes misses struct field dependencies. |
System headers removed from .h |
The .c file uses the types (e.g., va_list, uintptr_t) that come transitively. Moving the include to .c is correct; removing it entirely is not. |
app_settings.h removed |
May cause cascading failures — downstream files rely on transitive constants (e.g., DEFAULT_EXPOSURE_STEP). Fix: add direct includes in consumers. |
Recommended Workflow¶
- Run IWYU and save full output
- Categorize removals:
- System headers (
stdio.h,string.h, etc.) — usually safe, but test each - Project headers (
app_settings.h,shader.h) — verify symbol usage first gl_common.hreplacements — defer to dedicated header-fanout refactoring- Apply removals in batches, building after each batch
- Fix cascading issues: when removing a transitive include from a header, consumers may need a direct include added
- Run full test suite sequentially (
ctest --test-dir build --output-on-failure) - Verify format + lint are clean
Project Audit Results (April 2026)¶
The initial IWYU audit of suckless-ogl identified:
| Category | Count | Status |
|---|---|---|
| System header removals | 26 | Applied (5 were false positives) |
| Project header removals | 8 | Applied |
gl_common.h → glad/glad.h |
33 | Deferred (needs header-fanout refactoring) |
| False positives caught | 6 | sched.h, immintrin.h, stdarg.h+stdint.h in utils.h, string.h in perf_timer.c, pthread.h in tracy_manager.h |
| Cascading fixes needed | 2 | src/utils.c (+stdarg.h/stdint.h), src/postprocess_input.c (+app_settings.h) |
| Net includes removed | 33 | Across 29 files |
False Positive Rate¶
Out of 41 attempted removals, 6 were false positives (~15%). This confirms
IWYU suggestions should always be treated as advisory, not authoritative.
Two false positives (string.h in perf_timer.c, pthread.h in tracy_manager.h)
were caught by CI: they are used inside #ifdef TRACY_ENABLE blocks invisible to IWYU.
Integration with CI and Pre-Push¶
IWYU is integrated at two levels:
Pre-Push Hook (fast, on changed files)¶
The pre-push hook runs IWYU only on staged .c files, checking for
unused includes (should remove). Known false positives are filtered
through an allowlist in scripts/iwyu_check.sh.
# Manual invocation (same as pre-push hook)
just iwyu-check # Changed files vs origin/master
just iwyu-check feat/my-branch # Changed files vs specific ref
# Or directly
bash scripts/iwyu_check.sh --staged # Staged files only
bash scripts/iwyu_check.sh --changed # vs origin/master
bash scripts/iwyu_check.sh src/app.c src/io.c # Specific files
Typical runtime: 2–5 seconds per changed file.
CI Job (full scan, advisory)¶
The GitHub Actions lint-and-format job includes an advisory IWYU step
that scans all 76+ source files. It uses continue-on-error: true so it
reports findings without blocking the pipeline.
Typical runtime: 2–3 minutes for the full codebase.
Justfile Recipes¶
| Recipe | Description |
|---|---|
just iwyu |
Full scan (CI-grade, all src/*.c files) |
just iwyu-check [REF] |
Changed files only (vs origin/master or custom ref) |
Known False Positives (Allowlist)¶
The script maintains an allowlist in scripts/iwyu_check.sh for confirmed
false positives. Current entries:
| Pattern | Reason |
|---|---|
gl_common.h |
Provides RAII macros beyond glad.h |
cglm/cglm.h |
Umbrella header; IWYU wants sub-headers |
postprocess.h |
Transitive struct dependency from app.h |
sched.h |
struct sched_param in struct field |
immintrin.h |
AVX/F16C umbrella header |
GLFW/glfw3.h |
app_binding.h API is about GLFW bindings |
gpu_profiler.h |
GPUProfiler* in effect_benchmark.h struct |
app.h |
Defensive transitive include in app_ui.c |
string.h |
strlen in TRACY_ENABLE macro (perf_timer.c) |
pthread.h |
pthread_mutex_t in TRACY_ENABLE block (tracy_manager.h) |
Mapping File¶
The project includes .iwyu.imp — an IWYU mapping file that declares
gl_common.h as the public facade for glad/glad.h and cglm/cglm.h
as the umbrella for cglm sub-headers. This reduces noise in the raw
IWYU output.