[clang] `-fcoverage-mapping` corruption with `#include` in a function via `-isystem`
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
When a non-system source file textually `#include`s another file **inside a function body**, and that included file is found via `-isystem`, clang produces a corrupt `__llvm_covmap` section. `llvm-cov export` then fails with **"truncated coverage data"**.
The same compilation succeeds when `-isystem` is replaced with `-I`.
## Minimal reproducer
### Setup
```bash
mkdir -p sysinclude
cat > sysinclude/body.c <<'EOF'
result = 42;
EOF
cat > main.c <<'EOF'
int func(void) {
int result = 0;
#include "body.c"
return result;
}
EOF
# Build a valid profdata (needed for llvm-cov export)
cat > trivial.c <<'EOF'
int main(void) { return 0; }
EOF
clang -fprofile-instr-generate -fcoverage-mapping -o trivial trivial.c
LLVM_PROFILE_FILE=default.profraw ./trivial
llvm-profdata merge -o default.profdata default.profraw
```
### BUG: `-isystem` produces truncated coverage data
```bash
clang -c -fprofile-instr-generate -fcoverage-mapping -isystem sysinclude -o main.o main.c
llvm-cov export -format=lcov -object main.o -instr-profile default.profdata
```
Output:
```
error: failed to load coverage: 'main.o': truncated coverage data
```
### OK: `-I` works correctly
```bash
clang -c -fprofile-instr-generate -fcoverage-mapping -I sysinclude -o main.o main.c
llvm-cov export -format=lcov -object main.o -instr-profile default.profdata
```
Output:
```
SF:main.c
FN:1,func
...
end_of_record
SF:sysinclude/body.c
...
end_of_record
```
## Analysis
Dumping the `__llvm_covmap` and `__llvm_covfun` sections reveals the cause.
With `-isystem`, `body.c` is treated as a system header and is excluded from the `__llvm_covmap` filename table. However, clang still emits a `__llvm_covfun` record for `func()` whose coverage regions reference source locations in `body.c`. Since `body.c` is not in the filename table, the file-ID reference is out of bounds, and `llvm-cov` reports "truncated coverage data" when it tries to resolve it.
With `-I`, `body.c` is not a system header, so it appears in the filename table and the file-ID references are valid.
### Section comparison
```bash
# -isystem: 1 covfun record, small filename table (missing body.c)
llvm-objdump -h main_isystem.o | grep __llvm_cov
# __llvm_covfun
# __llvm_covmap
# -I: 1 covfun record, filename table includes body.c
llvm-objdump -h main_I.o | grep __llvm_cov
# __llvm_covfun
# __llvm_covmap
```
## More realistic example
This pattern appears in real-world C codebases. For example, [libpg_query](https://github.com/pganalyze/libpg_query) (PostgreSQL's parser extracted as a library) uses textual `#include` of `.c` files inside function bodies for code generation:
```c
// pg_query_fingerprint.c
int fingerprint(int tag, ...) {
switch (tag) {
#include "pg_query_fingerprint_conds.c" // 258 switch cases
}
}
```
When built with `-isystem` for the include paths (as Bazel does for external dependencies), four `.o` files produce corrupt coverage data, which causes `llvm-cov export` to abort entirely and produce empty coverage reports.
## Environment
- Ubuntu clang 20.1.8 (`apt` package `clang-20` on Ubuntu 25.10)
- Also reproduced with the corresponding `llvm-cov-20` / `llvm-profdata-20`
- Linux x86_64
Contributor guide
Research direction
Start by running the issue's minimal reproducer with main.c, sysinclude/body.c, clang -fcoverage-mapping, and llvm-cov export, comparing -isystem with -I. Then trace clang's coverage mapping generation for regions from included system files and inspect the emitted __llvm_covmap and __llvm_covfun sections. Done means the -isystem build produces valid coverage data and llvm-cov export completes, with a regression test for the reproducer.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100