Search code examples
c++gcovlcov

LCOV branches at the end of a function


enter image description here

What are the branches at the end of this function. How could I cover them?


Solution

  • You are observing the gcc generated code for the destruction of static storage duration (global) variables.

    Your coverage shows that the function foo has been entered three times, however the counter near the end of the scope shows that the code was executed eight times, including branches that you enquire about.

    Now you must consider that the compiler puts the header file in the translation unit and that gcov doesn't see your code exactly as it is, but rather as a control flow graph of assembly instruction with branching as the edges of the graph.

    Thus the "end of foo scope" in the lcov html output is not really the end of the foo method scope but rather everything that's included after foo as well in the entire translation unit, including the destruction of global variables that have been declared in the header file.

    The header itself hasn't been included in the question, but even the most basic __static_initialization_and_destruction assembly that gcc generates has a number of branches included.

    Note that you may have included global variables or you may have not - gcc still might generate this code for every translation unit.


    Look at the underlying output of gcov:

    function _Z3fooi called 1 returned 100% blocks executed 50%
            1:    4:int foo(int x) {
            1:    5:    if (x==1) {
    branch  0 taken 0% (fallthrough)
    branch  1 taken 100%
        #####:    6:        std::cout << "foo" << std::endl;
    call    0 never executed
    call    1 never executed
        #####:    7:        return 0;
            -:    8:    }
            1:    9:    return 1;
    function _GLOBAL__sub_D__Z3fooi called 1 returned 100% blocks executed 100%
    function _GLOBAL__sub_I__Z3fooi called 1 returned 100% blocks executed 100%
    function _Z41__static_initialization_and_destruction_0ii called 2 returned 100% blocks executed 100%
            6:   10:}
    call    0 returned 100%
    call    1 returned 100%
    branch  2 taken 50% (fallthrough)
    branch  3 taken 50%
    branch  4 taken 100% (fallthrough)
    branch  5 taken 0%
            -:   11:
    

    And look at the generated assembly, trimmed to clarify the point:

            ...
            ret
            .seh_endproc
            .def    _Z41__static_initialization_and_destruction_0ii;        .scl    3;      .type   32;     .endef
            .seh_proc       _Z41__static_initialization_and_destruction_0ii
    _Z41__static_initialization_and_destruction_0ii:
    .LFB978:
            ...
            mov     QWORD PTR __gcov0._Z41__static_initialization_and_destruction_0ii[rip], rax
            cmp     DWORD PTR 16[rbp], 1
            jne     .L5                                 <-- BRANCH
            mov     rax, QWORD PTR __gcov0._Z41__static_initialization_and_destruction_0ii[rip+8]
            add     rax, 1
            mov     QWORD PTR __gcov0._Z41__static_initialization_and_destruction_0ii[rip+8], rax
            cmp     DWORD PTR 24[rbp], 65535
            jne     .L5                                 <-- BRANCH
            ...
    .L5:
            cmp     DWORD PTR 16[rbp], 0
            je      .L6                                 <-- BRANCH