Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lcov --initial generates mis-hit for signature in function definition #30

Closed
jbibollet opened this issue May 24, 2017 · 15 comments
Closed

Comments

@jbibollet
Copy link

Versions

Running LCOV version 1.12 on macOS Sierra 10.12.5 and g++ Apple LLVM version 8.1.0 (clang-802.0.42)

Code snippet

Consider the following simple c++ code:

#include <iostream>

int myFn(int x) {
  return x * x;
}

int main(int argc, char *argv[]) {
  --argc, ++argv;

  std::cout << myFn(12) << std::endl;
  return 0;
}

Coverage result

   Line data    Source code
 1             : #include <iostream>
 2             :
 3           0 : int myFn(int x) {
 4           1 :   return x * x;
 5             : }
 6             :
 7           0 : int main(int argc, char *argv[]) { 
 8           1 :   --argc, ++argv;
 9             :
10           1 :   std::cout << myFn(12) << std::endl;
11           1 :   return 0;
12             : }

PROBLEM: lines 3 and 7 are reported executable but not hit, leading to mis coverage report.

Steps to reproduce

echo "--------------"
echo "-- Building --"
g++ --coverage -O0 -g0 test.cpp

echo "----------------------"
echo "-- Zeroing counters --"
lcov --zerocounters -d ./

echo "---------------------"
echo "-- Initial capture --"
lcov --capture --no-external --initial -d ./ --gcov-tool covwrap.sh -o base.info

echo "-------------"
echo "-- Running --"
./a.out

echo "---------------"
echo "-- Capturing --"
lcov --capture -d ./ --gcov-tool covwrap.sh -o test.info

echo "---------------"
echo "-- Combining --"
lcov -a base.info -a test.info -o total.info

echo "----------"
echo "-- HTML --"
genhtml total.info -o ./html

(where covwrap.sh is simply:

#!/bin/bash
exec llvm-cov-mp-3.9 gcov "$@"

Output

--------------
-- Building --
----------------------
-- Zeroing counters --
Deleting all .da files in ./ and subdirectories
Done.
---------------------
-- Initial capture --
Capturing coverage data from ./
Found gcov version: 3.9.1
Found LLVM gcov version 3.4, which emulates gcov version 4.2
Scanning ./ for .gcno files ...
Found 1 graph files in ./
Processing test.gcno
  ignoring data for external file /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__locale
  ignoring data for external file /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ios
  ignoring data for external file /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ostream
Finished .info-file creation
-------------
-- Running --
144
---------------
-- Capturing --
Capturing coverage data from ./
Found gcov version: 3.9.1
Found LLVM gcov version 3.4, which emulates gcov version 4.2
Scanning ./ for .gcda files ...
Found 1 data files in ./
Processing test.gcda
Finished .info-file creation
---------------
-- Combining --
Combining tracefiles.
Reading tracefile base.info
Reading tracefile test.info
Writing data to total.info
Summary coverage rate:
  lines......: 78.6% (11 of 14 lines)
  functions..: 100.0% (7 of 7 functions)
  branches...: no data found
----------
-- HTML --
Reading data file total.info
Found 4 entries.
Found common filename prefix "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++"
Writing .css and .png files.
Generating output.
Processing file v1/ostream
Processing file v1/__locale
Processing file v1/ios
Processing file /bb/tmp/test.cpp
Writing directory view page.
Overall coverage rate:
  lines......: 78.6% (11 of 14 lines)
  functions..: 100.0% (7 of 7 functions)
@oberpar
Copy link
Contributor

oberpar commented May 24, 2017

I tried to reproduce the problem with GNU g++ 4.8.5 but the result was as expected: both test.info and base.info contain an execution count of 1 for lines 3 and 7. Could you run llvm-cov directly (via covwrap.sh -abc test.gcda) on the test.gcda file and post the relevant portion of the resulting test.cpp.gcov file?

@jbibollet
Copy link
Author

The resulting test.cpp.gcov does not flag lines 3 and 7 as being instrumented/hit:

        -:    0:Source:test.cpp
        -:    0:Graph:test.gcno
        -:    0:Data:test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <iostream>
        -:    2:
function _Z4myFni called 1 returned 100% blocks executed 100%
        -:    3:int myFn(int x) {
        1:    4:  return x * x;
        1:    4-block  0
        -:    5:}
        -:    6:
function main called 1 returned 100% blocks executed 100%
        -:    7:int main(int argc, char *argv[]) {
        1:    8:  --argc, ++argv;
        -:    9:
        1:   10:  std::cout << myFn(12) << std::endl;
        1:   11:  return 0;
        1:   11-block  0
        -:   12:}

Full output of the llvm-cov -abv test.gcda:

covwrap.sh -abc test.gcda
File 'test.cpp'
Lines executed:100.00% of 4
No branches
No calls
test.cpp:creating 'test.cpp.gcov'

File '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ostream'
Lines executed:100.00% of 4
No branches
No calls
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ostream:creating 'ostream.gcov'

File '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ios'
Lines executed:50.00% of 2
Branches executed:66.67% of 6
Taken at least once:33.33% of 6
No calls
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ios:creating 'ios.gcov'

File '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__locale'
Lines executed:100.00% of 2
No branches
No calls
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__locale:creating '__locale.gcov'

@oberpar
Copy link
Contributor

oberpar commented Jun 1, 2017

It appears that the cause for this problem lies in geninfo's read_gcno_function_record() function that assumes that a function's starting line is always instrumented. This seems to be true for .gcno files generated by GCC, but not for those from LLVM.

A fix needs some more thought though, as the internal data representation for a .gcno file currently doesn't have a dedicated place for reporting function starting lines.

@aldanor
Copy link

aldanor commented Dec 24, 2017

Wondering if there is any workaround for this?

Initial capture indeed seems to assume that the function's starting line is instrumented, which then leads to false positives when they are marked as non-covered.

Edit: it seems as simple as filtering out all lines (where N is a line number) like

DA:N,0

where there exists a previous line in this file saying

FN:N,<function name>

Is there any catch? (re: properly fixing it in lcov itself)

@aldanor
Copy link

aldanor commented Dec 24, 2017

Here's a sample script that "fixes" it, for those who need it working now:

import argparse
import subprocess

def demangle(symbol):
    return subprocess.check_output(['c++filt', '-n', symbol.strip()]).decode().strip()

def filter_lcov(lines, verbose=False):
    defs, srcfile = {}, ''
    for line in lines:
        if line.startswith('SF:'):
            defs = {}
            srcfile = line[3:].strip()
        elif line.startswith('end_of_record'):
            defs = {}
        elif line.startswith('FN:'):
            lineno, symbol = line[3:].split(',')
            defs[lineno] = demangle(symbol)
        elif line.startswith('DA:'):
            lineno = line[3:].split(',')[0]
            if lineno in defs:
                if verbose:
                    print(f'Ignoring: {srcfile}:{lineno}:{defs[lineno]}')
                continue
        yield line

def main():
    p = argparse.ArgumentParser()
    p.add_argument('input', type=str)
    p.add_argument('output', type=str)
    p.add_argument('--verbose', '-v', action='store_true')
    args = p.parse_args()
    with open(args.input, 'r') as fin:
        lines = list(fin)
    with open(args.output, 'w') as fout:
        for line in filter_lcov(lines, verbose=args.verbose):
            fout.write(line)

if __name__ == '__main__':
    main()

(It would be obviously nice to not have to do it manually, though...)

@JeremyAgost
Copy link

Here's a faster version of your script that works with Objective-C++ sources and only demangles in verbose mode :)

import argparse
import subprocess

def demangle(symbol):
    p = subprocess.Popen(['c++filt','-n'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    return p.communicate(input=symbol.encode())[0].decode().strip()

def filter_lcov(lines, verbose=False):
    defs, srcfile = {}, ''
    for line in lines:
        if line.startswith('SF:'):
            defs = {}
            srcfile = line[3:].strip()
        elif line.startswith('end_of_record'):
            defs = {}
        elif line.startswith('FN:'):
            lineno, symbol = line[3:].split(',')
            if verbose:
                defs[lineno] = demangle(symbol)
            else:
                defs[lineno] = True
        elif line.startswith('DA:'):
            lineno = line[3:].split(',')[0]
            if lineno in defs:
                if verbose:
                    print(f'Ignoring: {srcfile}:{lineno}:{defs[lineno]}')
                continue
        yield line

def main():
    p = argparse.ArgumentParser()
    p.add_argument('input', type=str)
    p.add_argument('output', type=str)
    p.add_argument('--verbose', '-v', action='store_true')
    args = p.parse_args()
    with open(args.input, 'r') as fin:
        lines = list(fin)
    with open(args.output, 'w') as fout:
        for line in filter_lcov(lines, verbose=args.verbose):
            fout.write(line)

if __name__ == '__main__':
    main()

@Swift1313
Copy link

Can you release your workaround script with an open-source license? I want to use it.

@JeremyAgost
Copy link

@Swift1313 I published my version of the python script with a public domain license at https://github.com/JeremyAgost/lcov-llvm-function-mishit-filter

@Swift1313
Copy link

@aldanor Considering @JeremyAgost's script is based on your's, can you open-source that as well? Please?

@marehr
Copy link

marehr commented Jun 4, 2018

My g++ (arch linux) outputs the following

Coverage Result

   Line data    Source code
 1             : #include <iostream>
 2             : 
 3           1 : int myFn(int x) {
 4           1 :   return x * x;
 5             : }
 6             : 
 7           1 : int main(int argc, char *argv[]) {
 8           1 :   --argc, ++argv;
 9             : 
10           1 :   std::cout << myFn(12) << std::endl;
11           1 :   return 0;
12           0 : }

For me the last closing bracket will not count

@stevecheckoway
Copy link

Here's a one-liner that should do the same thing as the Python scripts. Awk isn't my forte, so there may be an easier method.

awk -F '[:,]' '/^SF:/ { delete defs } /^FN:/ { defs[$2]=1 } /^DA:/ { if ($3 == 0 && $2 in defs) next } { print }'

Explanation: -F '[:,]' sets the field separator FS to the regular expression [:,]. The defs array is deleted for every new source file. On each function definition, the line number is used as a key into defs (with an arbitrary value of 1). Each zero count for lines that are part of the definition is not printed whereas every other line is.

This is simple enough to place directly into a Makefile (but you do need to double the $ to pass them through make).

@popescu-af
Copy link

Hola, is there no fix for this yet?

@JeremyAgost
Copy link

JeremyAgost commented Mar 7, 2019 via email

fingolfin added a commit to fingolfin/gap that referenced this issue May 16, 2020
This should fix a bug in the coverage computation in older clang
versions (before LLVM 8) where the function signature is marked as
uncovered: clang instruments incorrectly marks it as code. That is:

    void foo(void) // <- this line is marked as uncovered code
    {
        return 0;
    }

See also
- <https://stackoverflow.com/q/31507084>
- <https://stackoverflow.com/q/47960954>
- <linux-test-project/lcov#30>
fingolfin added a commit to gap-system/gap that referenced this issue May 17, 2020
This should fix a bug in the coverage computation in older clang
versions (before LLVM 8) where the function signature is marked as
uncovered: clang instruments incorrectly marks it as code. That is:

    void foo(void) // <- this line is marked as uncovered code
    {
        return 0;
    }

See also
- <https://stackoverflow.com/q/31507084>
- <https://stackoverflow.com/q/47960954>
- <linux-test-project/lcov#30>
@henry2cox
Copy link
Collaborator

Hi -
This issue seems to have been lingering, open, for a very long time.
Does anyone know if it is still a problem?
If so - and if there is a testcase - then I will try to fix it (in the lcov sources - no external scripts or hacking).
Note that a there are a number of LLVM-11 related fixes in a recent TOT commit.
If it has been fixed by now: then I would like to close it.

Thanks

@oberpar
Copy link
Contributor

oberpar commented May 2, 2023

The problem mentioned in #30 (comment) refers to lcov code that directly interprets binary .gcno files. This code is no longer used for toolchains that support the intermediate gcov output format such as LLVM 11/Xcode 12.5 and GCC versions 5 and above, therefore this problem should no longer be present when using those versions.

Given the amount of effort that would be required to fix the gcno parsing code and the existing workaround for affected users to switch to more current toolchains, I'm closing this issue as won't fix.

@oberpar oberpar closed this as not planned Won't fix, can't repro, duplicate, stale May 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants