Skip to content

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.
  1. Run IWYU and save full output
  2. Categorize removals:
  3. System headers (stdio.h, string.h, etc.) — usually safe, but test each
  4. Project headers (app_settings.h, shader.h) — verify symbol usage first
  5. gl_common.h replacements — defer to dedicated header-fanout refactoring
  6. Apply removals in batches, building after each batch
  7. Fix cascading issues: when removing a transitive include from a header, consumers may need a direct include added
  8. Run full test suite sequentially (ctest --test-dir build --output-on-failure)
  9. 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.

# Local equivalent of the CI scan
just iwyu                # Full scan, all src/*.c, verbose

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.