llvm / llvm/llvm-project

Incorrect coverage counts for code after exception throw sites

Open
#175,972 0 comments 0 reactions 0 assignees View on GitHub
coverage
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

# Description

## Summary

LLVM's source-based code coverage incorrectly reports execution counts for code that never executes when it appears after an exception throw site in the same scope (but outside the throwing function).

## Environment

- **Clang Version**: Ubuntu clang version 21.1.0 (++20250729101116+317403d705ac-1~exp1~20250729101218.12)
- **Target**: x86_64-pc-linux-gnu
- **Platform**: Linux 6.8.0-87-generic x86_64 Ubuntu
- **Build Flags**: `-fprofile-instr-generate -fcoverage-mapping`

## Description

When an exception is thrown inside a try block, coverage instrumentation incorrectly reports that subsequent code in the same scope (after the function call that throws) has executed, even though the exception immediately transfers control to the catch block.

The coverage **correctly** shows 0 executions for code *inside* the throwing function after the throw statement, but **incorrectly** shows non-zero counts for code at the *call site* after the throwing function call.

## Minimal Reproducer

```cpp
// coverage_bug_repro.cpp
#include

int i{};

void always_throws()
{
throw 42;
++i; // Doesn't execute, coverage correctly shows 0 }

int main()
{
try {
always_throws();
++i; // Doesn't execute, but coverage shows 1!
}
catch(...)
{
}

std::cout << i << '\n'; // outputs: 0
}
```## Steps to Reproduce

```bash
# Compile with coverage instrumentation
clang++ -fprofile-instr-generate -fcoverage-mapping coverage_bug_repro.cpp
# Run the program and capture profile
LLVM_PROFILE_FILE="repro.profraw" ./a.out

# Merge profile data
llvm-profdata merge -sparse repro.profraw -o repro.profdata
# Generate coverage report
llvm-cov show -instr-profile=repro.profdata ./a.out
```

## Expected Behavior

Line 17 (`++i;` after the `always_throws()` call) should show **0 executions** because:
- The exception is thrown on line 8
- Control immediately transfers to the catch block
- The code on line 17 never executes
- The program outputs `0`, proving `i` was never incremented

## Actual Behavior

**Coverage report shows:**

```
15| 1| try
16| 1| { 17| 1| always_throws();
18| 1| ++i; // BUG: Shows execution count of 1
19| 1| }
```
Line 18 shows execution count of **1**, even though it never executed.

**For comparison**, line 9 (inside `always_throws()` after the throw) correctly shows **0**:

```
7| 1|{
8| 1| throw 42;
9| 0| ++i; // Correctly shows 0
10| 0|}
```

**Proof**: The program outputs `0`, definitively proving that `++i` on line 18 never executed.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.