Bug 48463 - gcov does not handle C++ clones (deleting ctors, template instantiations)
Summary: gcov does not handle C++ clones (deleting ctors, template instantiations)
Alias: None
Product: gcc
Classification: Unclassified
Component: gcov-profile (show other bugs)
Version: 4.3.3
: P3 normal
Target Milestone: ---
Assignee: Martin Liška
Depends on:
Reported: 2011-04-05 15:26 UTC by Christian Feist
Modified: 2017-11-09 09:19 UTC (History)
1 user (show)

See Also:
Known to work:
Known to fail:
Last reconfirmed: 2016-08-05 00:00:00

1.png, 2.png, src/ (has 3 source files) (42.56 KB, application/x-gzip)
2011-04-05 15:26 UTC, Christian Feist

Note You need to log in before you can comment on or make changes to this bug.
Description Christian Feist 2011-04-05 15:26:59 UTC
Created attachment 23882 [details]
1.png, 2.png, src/ (has 3 source files)

Gcov is generating a lot of useless output when it comes to function coverage. The problem really came to our attention when looking at classes with virtual destructors like the one in the following class example:

class ClassA
        virtual ~ClassA();

class ClassB : public ClassA

The list of function names then contains three destructors for ClassA (two of which are apparently not called, one which is) and three destructors for ClassB (two of which are apparently not called, one which is). They all have the same signature. When disabling then genhtml option --demangle-cpp, or by viewing the .gcov files, you can see, that each of these destructors (of ClassA for example) has a different name. Example (excerpt of gcov file for classA.cpp):

Function '_ZN6ClassAD0Ev'
Lines executed:0.00% of 3
Branches executed:0.00% of 2
Taken at least once:0.00% of 2
Calls executed:0.00% of 3

Function '_ZN6ClassAD1Ev'
No executable lines
Branches executed:0.00% of 2
Taken at least once:0.00% of 2
Calls executed:0.00% of 3

Function '_ZN6ClassAD2Ev'
No executable lines
Branches executed:100.00% of 2
Taken at least once:50.00% of 2
Calls executed:66.67% of 3

The mangled names differ only in the parts: D0, D1, D2. It's odd that one destructor apparently has 3 executable lines (in the attached source, you can see it's only one line), and that the two others have no executable lines, yet they have branches that can be covered. 
These "IDs" 'D0', 'D1', 'D2' are given to the mangled function names by the gcc compiler, yet I am unsure of what the difference is supposed to be. 
Nevertheless, it isn't really of interest to the person analyzing the coverage of his code, what gcc generates in the background. Only those desctructors, that the programmer can actually see in HIS code, should show up in the list of covered (or not covered) functions. 
This goes for the other functions that show up in the example's list as well:
global destructors keyed to _ZN6ClassAC2Ev 
global constructors keyed to _ZN6ClassAC2Ev
__static_initialization_and_destruction_0(int, int)

It leads to wrong numbers for the function and branch coverage of what you really want to see.

To reproduce:

I wrote a two classes: ClassA and ClassB. ClassA has a public constructor (no args, just prints something) and a public virtual destructor (no args, just prints something). ClassB inherits from ClassA (publicly) and has a public constructor (no args, just prints something) and a public destructor (no args, just prints something). The main file just creates an instance of ClassB, stores it in a pointer to ClassA and calls delete on that pointer variable. 

The source files were compiled using:
g++ -fprofile-arcs -ftest-coverage -o prog main.cpp classA.cpp

Then ./prog was run once.

A .info file was generated using lcov (version 1.9) in the following command:
lcov -d . -c -f -o NewInfo.info

The final html overview was generated using genhtml (also version 1.9) in the following command:
genhtml --demangle-cpp --prefix . -o html/ NewInfo.info 

Alternatively, to reproduce, you can also use the attached source files. The attachment also include two pictures, displaying a listing of all the functions that have apparently been covered or not covered.

Hope this info helps solve the problem. 
Comment 1 Andrew Pinski 2011-04-05 19:02:57 UTC
>The mangled names differ only in the parts: D0, D1, D2. 

This is because the ABI defines these names.  IIRC this has been improved in 4.5 or 4.6 where only one gets defined now and the rest are aliases.
Comment 2 Christian Feist 2011-04-11 15:30:19 UTC
Thank you for your reply.

Using GCC 4.6 (and with it the newest version of GCOV), does generate a slightly different output, but it doesn't show any improvement in terms of function coverage. I'm still getting multiple destructors, which seem to differ from one another (though I don't see 'D1' here):

function _ZN6ClassAD0Ev called 0 returned 0% blocks executed 0%
function _ZN6ClassAD2Ev called 1 returned 100% blocks executed 80%
        1:    9:ClassA::~ClassA()
        -:   10:{
        1:   11:        std::cout << "Bey, ClassA!" << std::endl;
call    0 returned 100%
call    1 returned 100%
        1:   12:}
call    0 never executed
call    1 never executed
branch  2 taken 0% (fallthrough)
branch  3 taken 100%
call    4 never executed

I'm not sure what all this stuff about branches is that I have going on here, but I don't have an in my destructor, so I assume it is code generated by GCC in the generated destructors that are showing up in the coverage. It might be useful to know that these exist in the background, but this info is confusing and useless when analyzing ones own code coverage.

On a side note: These also show up when generating documents with lcov.

My course of action was the same as before, also leaving the source files unaltered.
Comment 3 Martin Liška 2016-08-05 13:19:07 UTC
I confirm that, it's caused by generated deleting dtor:

virtual ClassB::~ClassB() (struct ClassB * const this)
  <bb 2>:
  ClassB::~ClassB (this_2(D));
  operator delete (this_2(D), 8);


The dtor is given the same source line as it's abstract origin and all statements in the function are given line after the origin. Resulting
in bogus output:
$  gcov -b tc.gcda

function _ZN6ClassBD0Ev called 1 returned 100% blocks executed 100%
function _ZN6ClassBD2Ev called 1 returned 100% blocks executed 100%
        3:   10:ClassB::~ClassB()
        -:   11:{
        1:   12:	std::cout << "Bey, ClassB!" << std::endl;
call    0 returned 100%
call    1 returned 100%
        2:   13:}
call    0 returned 100% // ClassB::~ClassB (this_2(D));
call    1 returned 100% // operator delete (this_2(D), 8);
        -:   14:

First idea about how to fix the issues is to ignore all fns with an abstract origin?

diff --git a/gcc/coverage.c b/gcc/coverage.c
index d4d371e..0cf5ebb 100644
--- a/gcc/coverage.c
+++ b/gcc/coverage.c
@@ -642,6 +642,11 @@ coverage_begin_function (unsigned lineno_checksum, unsigned cfg_checksum)
   if (no_coverage || !bbg_file_name)
     return 0;
+  /* Do not output any abstract origin function.  */
+  tree abstract = DECL_ABSTRACT_ORIGIN (current_function_decl);
+  if (abstract)
+    return 0;
   xloc = expand_location (DECL_SOURCE_LOCATION (current_function_decl));
   /* Announce function */

I'm going to discuss that with Honza.
Comment 4 Martin Liška 2017-10-20 10:52:53 UTC
Example of template instantiation:

$ cat test.cpp
template<class T>
class Foo
    b = 123;

  void test() { b = 111; }

  int b;

template class Foo<float>;
template class Foo<int>;

int main()
  Foo<int> xx;

  return 0;

$ cat test.cpp.gcov:

        -:    1:template<class T>
        -:    2:class Foo
        -:    3:{
        -:    4:  public:
        1:    5:  Foo()
        -:    6:  {
        1:    7:    b = 123;
        1:    8:  }
        -:    9:
        1:   10:  void test() { b = 111; }
        -:   11:
        -:   12:  private:
        -:   13:  int b;
        -:   14:};
        -:   15:
        -:   16:template class Foo<float>;
        -:   17:template class Foo<int>;
        -:   18:
        1:   19:int main()
        -:   20:{
        1:   21:  Foo<int> xx;
        1:   22:  xx.test();
        -:   23:
        1:   24:  return 0;
        -:   25:}

However LLVM does:

    1|       |template<class T>
    2|       |class Foo
    3|       |{
    4|       |  public:
    5|       |  Foo()
    6|      1|  {
    7|      1|    b = 123;
    8|      1|  }
  | Unexecuted instantiation: _ZN3FooIfEC2Ev
  | _ZN3FooIiEC2Ev:
  |    6|      1|  {
  |    7|      1|    b = 123;
  |    8|      1|  }
    9|       |
   10|      1|  void test() { b = 111; }
  | Unexecuted instantiation: _ZN3FooIfE4testEv
  | _ZN3FooIiE4testEv:
  |   10|      1|  void test() { b = 111; }
   11|       |
   12|       |  private:
   13|       |  int b;
   14|       |};
   15|       |
   16|       |template class Foo<float>;
   17|       |template class Foo<int>;
   18|       |
   19|       |int main()
   20|      1|{
   21|      1|  Foo<int> xx;
   22|      1|  xx.test();
   23|      1|
   24|      1|  return 0;
   25|      1|}

Which is more precise.
Comment 5 Martin Liška 2017-10-20 10:55:53 UTC
They basically provide info for all clones of a function:

  | _ZN3FooIfEC2Ev:
  |    6|      2|  {
  |    7|      2|    b = 123;
  |    8|      2|  }
  | _ZN3FooIiEC2Ev:
  |    6|      1|  {
  |    7|      1|    b = 123;
  |    8|      1|  }
Comment 6 Martin Liška 2017-10-20 10:57:12 UTC
GCC also has information that there are multiple functions pointing to a same line of code:

$ gcov-dump test.gcno | grep FUNCTION
test.gcno: 01000000:  11:FUNCTION ident=108032747, lineno_checksum=0xc563f9a6, cfg_checksum=0x21286892, `main' test.cpp:19
test.gcno: 01000000:  14:FUNCTION ident=1482441317, lineno_checksum=0xa9f23a94, cfg_checksum=0xa43083b8, `_ZN3FooIiE4testEv' test.cpp:10
test.gcno: 01000000:  13:FUNCTION ident=402991310, lineno_checksum=0x0169e3ec, cfg_checksum=0xa43083b8, `_ZN3FooIiEC2Ev' test.cpp:5
test.gcno: 01000000:  14:FUNCTION ident=1097691245, lineno_checksum=0x30c3089c, cfg_checksum=0xa43083b8, `_ZN3FooIfE4testEv' test.cpp:10
test.gcno: 01000000:  13:FUNCTION ident=56186600, lineno_checksum=0x9a359dca, cfg_checksum=0xa43083b8, `_ZN3FooIfEC2Ev' test.cpp:5

What we need to do is to properly present that.
Comment 7 Martin Liška 2017-11-09 09:11:49 UTC
Author: marxin
Date: Thu Nov  9 09:11:17 2017
New Revision: 254562

URL: https://gcc.gnu.org/viewcvs?rev=254562&root=gcc&view=rev
GCOV: support multiple functions per a line (PR gcov-profile/48463)

2017-11-09  Martin Liska  <mliska@suse.cz>

	PR gcov-profile/48463
	* coverage.c (coverage_begin_function): Output also end locus
	of a function and information whether the function is
	* gcov-dump.c (tag_function): Parse and print the information.
	* gcov.c (INCLUDE_MAP): Add include.
	(INCLUDE_SET): Likewise.
	(struct line_info): Move earlier in the source file because
	of vector<line_info> in function_info structure.
	(line_info::line_info): Likewise.
	(line_info::has_block): Likewise.
	(struct source_info): Add new member index.
	(source_info::get_functions_at_location): New function.
	(function_info::group_line_p): New function.
	(output_intermediate_line): New function.
	(output_intermediate_file): Use the mentioned function.
	(struct function_start): New.
	(struct function_start_pair_hash): Likewise.
	(process_file): Add code that identifies group functions.
	Assign lines either to global or function scope.
	(generate_results): Skip artificial functions.
	(find_source): Assign index for each source file.
	(read_graph_file): Read new flag artificial and end_line.
	(add_line_counts): Assign it either to global of function scope.
	(accumulate_line_counts): Isolate core of the function to
	accumulate_line_info and call it for both function and global
	scope lines.
	(accumulate_line_info): New function.
	(output_line_beginning): Fix GNU coding style.
	(print_source_line): New function.
	(output_line_details): Likewise.
	(output_function_details): Likewise.
	(output_lines): Iterate both source (global) scope and function
	(struct function_line_start_cmp): New class.
	* doc/gcov.texi: Reflect changes in documentation.

Comment 8 Martin Liška 2017-11-09 09:19:12 UTC