GCC's branch coverage statistics depend on machine code level branching, not just on branches that are explicit in the source code (if/else, switch/case, for, while, ||, &&, ?:, …). The compiler often has to insert extra branches in order to implement language features, especially in C++:
- static initialization (C and C++)
- destructors
- exception handling
- safety checks such as bound checks (aren't generally used)
If GCC is allowed to do optimizations, this can perhaps eliminate some of these branches. That is why using -O1
can sometimes help with coverage data. On the other hand, this makes it more difficult for gcov to attribute coverage data to the correct source code lines.
All of the machine code branches can theoretically be taken. If one of these branches is uncovered, that indicates an untested state transition. From the perspective of the compiler there isn't a big difference between an if-statement or calling a function that can throw. But for you, these are not equivalent: you are likely only interested in testing explicit branches.
Depending on your quality requirements that is a reasonable position to take. Exhaustively testing all machine code branches is impossible without advanced techniques such as fault injection. I personally recommend against ignoring compiler-inserted branches because it gives a false sense of security.
So we have to accept that 50% branch coverage is for free just from good statement coverage, but 100% branch coverage is unattainable. In the context of C++, branch coverage is probably best used when looking at line-by-line coverage, not as an aggregate statistic.
GCC does allow you to compile the software without exception handling, by using the -fno-exceptions
flag. Instead of throwing an exception, the process will abort directly. But this effectively switches you to an incompatible C++ dialect. Things like try/catch will no longer work; error checking in the standard library changes. So you cannot compile your software without exceptions just for code coverage purposes.
Fortunately, GCC marks branches that were added for exception handling. Since gcovr 4.2 (not yet released) you can use the gcov --exclude-throw-branches
flag to ignore uncovered exception-only branches. This is not perfect: it doesn't just exclude implicit branches to some exception handling pad, but also branches to an explicit catch
clause. This might hide uncovered uncovered that you do want to test.
Gcovrs also offers the --exclude-unreachable-branches
option. This removes branch coverage data if they are attributed to a line that does not seem to contain useful code. Again, this may exclude important coverage data, for example if coverage of a multi-line statement is attributed to a line that doesn't contain useful code. However, this does often help to exclude branches inserted due to static variables.