Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ if(BUILD_TESTING)
)
endforeach()

# ── Multi-file / whole-project integration tests ──────────────────────
# Tests that verify pagurus-check and pagurus.mk work correctly across
# a small multi-translation-unit project (tests/multifile/).
set(PAGURUS_MULTIFILE_RUNNER "${PAGURUS_TEST_DIR}/run_multifile_tests.sh")
set(PAGURUS_MULTIFILE_DIR "${PAGURUS_TEST_DIR}/multifile")

add_test(
NAME "pagurus_multifile"
COMMAND bash "${PAGURUS_MULTIFILE_RUNNER}"
"$<TARGET_FILE:pagurus_plugin>"
"${PAGURUS_MULTIFILE_DIR}"
)
set_tests_properties("pagurus_multifile" PROPERTIES
ENVIRONMENT "CLANG=${CLANG_EXECUTABLE}"
)

message(STATUS
"Plugin tests registered: ${CMAKE_CURRENT_BINARY_DIR} "
"(run with: ctest --output-on-failure)")
Expand Down
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,134 @@ structs) are automatically classified as **copy-able** and never receive E021.

---

## Multi-file project integration

Pagurus analyses one translation unit per `clang` invocation. All
intra-file checks (E001–E021) and inter-procedural summaries that are
visible through headers work without any extra setup. Two tools make
it straightforward to run the checker over an entire Makefile-based
project.

### `pagurus-check` — standalone script

`pagurus-check` (located at the root of this repository) runs the
plugin on many source files in a single command and aggregates the
results:

```bash
# Check two files; include/ is on the search path.
./pagurus-check --plugin=./build/pagurus_plugin.so \
--cflags="-Iinclude" \
src/main.c src/widget.c

# Scan every .c file under src/ with 4 parallel jobs.
./pagurus-check --plugin=./build/pagurus_plugin.so \
--cflags="-Iinclude -DNDEBUG" \
--jobs=4 --dir=src

# Use a compilation database generated by `bear make`.
# Per-file include paths and defines are extracted automatically.
bear make
./pagurus-check --plugin=./build/pagurus_plugin.so \
--compile-db=compile_commands.json

# Dry-run: report all diagnostics without writing .pagurus.c files.
./pagurus-check --plugin=./build/pagurus_plugin.so \
--dry-run --dir=src
```

`pagurus-check` exits with code **0** if all files pass (no E0xx
diagnostics) and **1** if any file has errors.

Full option reference:

```
Usage: pagurus-check [OPTIONS] [FILE...]
pagurus-check [OPTIONS] --dir=DIR
pagurus-check [OPTIONS] --compile-db=compile_commands.json

-p PATH, --plugin=PATH pagurus_plugin.so [./build/pagurus_plugin.so]
-C CMD, --clang=CMD Clang executable [clang]
-f FLAGS,--cflags=FLAGS Extra clang flags (e.g. "-DFOO -Iinclude")
-d DIR, --dir=DIR Scan DIR recursively for *.c files
-b FILE, --compile-db=FILE JSON compilation database
-j N, --jobs=N Parallel jobs [1]
--dry-run Dry-run: report but don't write .pagurus.c
--ir-pass Enable LLVM IR analysis (-fpass-plugin=)
-h, --help Show help
```

Environment variables `PAGURUS_PLUGIN` and `PAGURUS_CLANG` provide
defaults for `--plugin` and `--clang`.

### `pagurus.mk` — Makefile include

Drop `pagurus.mk` (also at the repository root) into an existing
Makefile project:

```makefile
# myproject/Makefile
CC = clang
CFLAGS = -Wall -Iinclude
SOURCES = src/main.c src/widget.c src/util.c

# Pagurus integration: define PAGURUS_PLUGIN before the include.
PAGURUS_PLUGIN = /path/to/build/pagurus_plugin.so
include /path/to/pagurus.mk
```

Available targets:

```bash
make pagurus-check # compile mode — borrow-check all PAGURUS_SOURCES
make pagurus-dry-run # dry-run mode — inspect only, no .pagurus.c written
make pagurus-clean # remove *.pagurus.c artefacts
```

Configurable variables (set them before the `include` line):

| Variable | Default | Description |
|---|---|---|
| `PAGURUS_PLUGIN` | `<pagurus.mk dir>/build/pagurus_plugin.so` | Plugin path |
| `PAGURUS_CLANG` | `clang` | Clang executable |
| `PAGURUS_SOURCES` | `$(SOURCES)` if defined, else `*.c` | Files to check |
| `PAGURUS_CFLAGS` | `$(CFLAGS)` | Extra flags passed to clang |
| `PAGURUS_JOBS` | `1` | Parallel jobs |
| `PAGURUS_IR_PASS` | `0` | Set to `1` to enable `-fpass-plugin=` |

### Generating a compilation database from a Makefile

When source files are compiled with different flags (include paths,
defines, or per-file options), the most accurate approach is to
generate a [compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html)
and pass it to `pagurus-check`:

```bash
# Install bear (Ubuntu)
sudo apt install bear

# Capture compile commands from a regular make run
bear -- make

# Run pagurus on every file with the exact flags used during compilation
./pagurus-check --plugin=./build/pagurus_plugin.so \
--compile-db=compile_commands.json
```

`bear` works with any build system that invokes a C compiler, including
hand-written Makefiles, autotools, and meson.

### Scope of per-TU analysis

| Check | Works within one TU | Notes |
|---|---|---|
| E001–E018 (AST checks) | ✅ | Full NLL + CFG within the TU |
| Inter-procedural summaries | ✅ when callee definition is visible | Header-only inlines or same-TU functions get full summaries; opaque `extern` calls get conservative assumptions |
| E019–E021 (drop semantics) | ✅ | Drop annotations propagate via headers |
| IR-E001/E002/E015/E018 | ✅ (requires `-fpass-plugin=`) | One IR module per `clang -c` invocation |

---

## Architecture

```
Expand Down
Loading
Loading