ankurrj7 wrote:
Adding a more detailed summary of the scope and stack, since this is one part
of a larger coverage-mapping cleanup.
## What this PR fixes
This PR fixes source-based coverage mapping when a statement is followed by
code that is unreachable because the statement inevitably sinks control flow,
but the immediate call expression is not itself explicitly marked `noreturn`.
The motivating case is a wrapper or call chain like:
```c
#include <stdio.h>
#include <stdlib.h>
static void die(void) {
exit(0);
}
static void wrapper(void) {
die();
puts("after die"); // should be 0
}
int main(void) {
wrapper();
puts("after wrapper"); // should be 0
}
```
Before this change, coverage mapping could keep the post-call source range in
the same live region as the call, so `puts("after die")` or `puts("after
wrapper")` could be shown as covered even though the program cannot reach those
lines.
This is a coverage mapping fix, not a runtime/code-generation behavior change.
The generated program behavior is unchanged; the change is in how Clang decides
where to terminate source coverage regions.
## Fix approach
The patch extends the existing `noreturn` handling in `CoverageMappingGen.cpp`
with conservative CFG-based sink detection:
- Direct `noreturn` calls still terminate the current coverage region as before.
- A direct callee with a visible body is also treated as terminating when its
CFG cannot reach a normal exit.
- This handles simple wrapper chains such as `wrapper -> die -> exit`.
- Infinite loops with no normal exit are detected through statement/CFG
analysis, so code after the loop is mapped as unreachable.
- Function sink results are cached and guarded against recursion with a
`Computing` state.
- Virtual dispatch remains conservative: a virtual call is not treated as
sinking just because the statically visible method body sinks, since a runtime
override may return normally.
Example of the intended result:
```text
wrapper(); // 1
puts("after wrapper"); // 0
```
and for an infinite loop:
```c++
int f() {
while (true) {
}
return 0; // should be 0 / unreachable
}
```
## Tests run
- `git diff --check`
- `git-clang-format --diff origin/main HEAD --extensions cpp --
clang/lib/CodeGen/CoverageMappingGen.cpp
clang/test/CoverageMapping/terminate-statements.cpp --diff_from_common_commit`
- Syntax-only compile of `clang/lib/CodeGen/CoverageMappingGen.cpp`
- Coverage-mapping checks in
`clang/test/CoverageMapping/terminate-statements.cpp` for:
- direct sink calls
- nested sink-call chains
- infinite `while` loops
- infinite `for` loops
- virtual-dispatch non-regression
- Runtime coverage repro for `main -> wrapper -> exit(0)` style code
- Runtime coverage repro for the virtual-dispatch conservative case
- Coverage regression suites on the local combined stack:
- `clang/test/CoverageMapping`: 75 passed
- `clang/test/Coverage`: 15 passed
## Stack
This PR is PR1 in the stack and is intentionally limited to CFG-proven sink
termination.
- PR1 / this PR: `cov-sink-termination`
Fixes direct/nested sink calls and infinite-loop sink coverage mapping.
- PR2 branch: `cov-explicit-skip-counters`
Follow-up for explicit skip-counter handling around abnormal exits and
`returns_twice` / `setjmp`-style control flow.
- PR3 branch: `cov-static-fp-sink`
Follow-up for simple static function-pointer sink cases.
At the moment, only this PR appears to be open upstream; the PR2 and PR3
branches exist on the fork as stacked follow-ups.
https://github.com/llvm/llvm-project/pull/200190
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits