c++g++code-coveragegcovgcovr

Why does GCOV claim this line is not covered?


I recently upgraded to a new version of gcc/g++/gcov, and now gcov behaves strangely. The new version claims some lines of code are not covered that were seen as covered by the old version. I managed to reduce my code to this minimal example.

#include <memory>
using namespace std;
struct S {};

int main() {
    unique_ptr<S> s;
    s = make_unique<S>();
}

I then compile this file using g++ -O0 -Wall -Wextra -Werror --std=c++17 --coverage, run the resulting a.out, then run gcov.

The resulting .gcov file contains:

    -:    0:Runs:1
    -:    1:#include <memory>
    -:    2:using namespace std;
    -:    3:struct S {};
    -:    4:
    1:    5:int main() {
#####:    6:    unique_ptr<S> s;
    1:    7:    s = make_unique<S>();
    1:    8:}

This is different from an older version of gcov, that claimed line 6 was hit 2 times.

Why does gcov think line 6 is not covered? Am I doing something wrong?

I am able to reproduce this behavior with gcc8, gcc9, and gcc10. gcc7 behaves as expected.

Compiler explorer comparing gcc7/8: https://godbolt.org/z/Te57s4WK8


Solution

  • Destructors. Fix by updating to the most recent GCC, or by using additional tools to filter out the coverage.

    The assembly generated by both compilers is almost identical, but the assembly is not the full picture here. The relevant part is how gcov associates parts of the assembly code to the source code. Gcov does not use debug information for this. Instead, gcov instruments the code to increment counters whenever a basic block is entered. Blocks are part of the assembly code that have no internal control flow. Gcov uses its gcno files to associate counter IDs with parts of the source code.

    Both compiler versions (GCC 7 and 8) produce three snippets of assembly that relate to the std::unique_ptr<S> s; declaration:

    It now happens that the first two snippets do not form their own block. They are part of blocks that include code from the surrounding lines. In particular, the code for the gcov counter is attributed in the debug info to the other lines. Only the block for the exception handler line contains a counter that is clearly attributed to the line in question. Thus, it seems understandable that different GCC versions exhibit different degrees of confusion. The confusion seems to be even bigger since gcov does not mark the uncovered code as exception-only.

    Ultimately, it is not really possible to figure out what gcov is “thinking”. In my experience, exceptions are the bane of good code coverage with GCC, since exceptions require extra code paths. In some cases it's possible to compile with -fno-exceptions. That will get you perfect code coverage, but it changes the language significantly. I don't like this.

    Compiling with GCC 11 seems to fix your problem. It produces different assembly compared to GCC 8, 9, or 10. The coverage report will show all lines as covered, and correctly note that one block is uncovered (if you use gcov -a).

    If you cannot switch to a more recent GCC, you could consider using third party tools to exclude such lines from coverage. E.g. gcovr and lcov let you annotate such lines with an exclusion marker, e.g.

    std::unique_ptr<S> s;  // LCOV_EXCL_LINE
    

    Gcovr also lets you define a custom regex. For example, you could use:

    gcovr --exclude-lines-by-pattern '(?x) ^ \s* unique_ptr<.*> \s* \w+; $'