From 136ca74eb91c225ff18a7b08e0e7fd027b88517c Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Thu, 9 Nov 2017 10:11:17 +0100 Subject: [PATCH] GCOV: support multiple functions per a line (PR gcov-profile/48463) 2017-11-09 Martin Liska PR gcov-profile/48463 * coverage.c (coverage_begin_function): Output also end locus of a function and information whether the function is artificial. * 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 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 scope. (struct function_line_start_cmp): New class. * doc/gcov.texi: Reflect changes in documentation. From-SVN: r254562 --- gcc/ChangeLog | 39 +++ gcc/coverage.c | 7 + gcc/doc/gcov.texi | 329 +++++++++++++----- gcc/gcov-dump.c | 8 +- gcc/gcov.c | 855 +++++++++++++++++++++++++++++++--------------- 5 files changed, 882 insertions(+), 356 deletions(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 982b43bb291..12fbaa0ac73 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,42 @@ +2017-11-09 Martin Liska + + PR gcov-profile/48463 + * coverage.c (coverage_begin_function): Output also end locus + of a function and information whether the function is + artificial. + * 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 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 + scope. + (struct function_line_start_cmp): New class. + * doc/gcov.texi: Reflect changes in documentation. + 2017-11-09 Jakub Jelinek PR debug/82837 diff --git a/gcc/coverage.c b/gcc/coverage.c index 8a56a677f15..f57897b8314 100644 --- a/gcc/coverage.c +++ b/gcc/coverage.c @@ -663,8 +663,15 @@ coverage_begin_function (unsigned lineno_checksum, unsigned cfg_checksum) gcov_write_unsigned (cfg_checksum); gcov_write_string (IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (current_function_decl))); + gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl)); gcov_write_filename (xloc.file); gcov_write_unsigned (xloc.line); + gcov_write_unsigned (xloc.column); + + expanded_location endloc = expand_location (cfun->function_end_locus); + + /* Function can start in a single file and end in another one. */ + gcov_write_unsigned (endloc.file == xloc.file ? endloc.line : xloc.line); gcov_write_length (offset); return !gcov_is_error (); diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi index 5c4ba8a51a7..6f6a92c131a 100644 --- a/gcc/doc/gcov.texi +++ b/gcc/doc/gcov.texi @@ -193,7 +193,7 @@ Write counts in human readable format (like 24k). @smallexample file:@var{source_file_name} -function:@var{line_number},@var{execution_count},@var{function_name} +function:@var{start_line_number},@var{end_line_number},@var{execution_count},@var{function_name} lcount:@var{line number},@var{execution_count},@var{has_unexecuted_block} branch:@var{line_number},@var{branch_coverage_type} @@ -201,24 +201,55 @@ Where the @var{branch_coverage_type} is notexec (Branch not executed) taken (Branch executed and taken) nottaken (Branch executed, but not taken) +@end smallexample There can be multiple @var{file} entries in an intermediate gcov file. All entries following a @var{file} pertain to that source file -until the next @var{file} entry. -@end smallexample +until the next @var{file} entry. If there are multiple functions that +start on a single line, then corresponding lcount is repeated multiple +times. Here is a sample when @option{-i} is used in conjunction with @option{-b} option: @smallexample -file:array.cc -function:11,1,_Z3sumRKSt6vectorIPiSaIS0_EE -function:22,1,main -lcount:11,1,0 -lcount:12,1,0 -lcount:14,1,0 -branch:14,taken -lcount:26,1,0 -branch:28,nottaken +file:tmp.cpp +function:7,7,0,_ZN3FooIcEC2Ev +function:7,7,1,_ZN3FooIiEC2Ev +function:8,8,0,_ZN3FooIcE3incEv +function:8,8,2,_ZN3FooIiE3incEv +function:18,37,1,main +lcount:7,0,1 +lcount:7,1,0 +lcount:8,0,1 +lcount:8,2,0 +lcount:18,1,0 +lcount:21,1,0 +branch:21,taken +branch:21,nottaken +lcount:23,1,0 +branch:23,taken +branch:23,nottaken +lcount:24,1,0 +branch:24,taken +branch:24,nottaken +lcount:25,1,0 +lcount:27,11,0 +branch:27,taken +branch:27,taken +lcount:28,10,0 +lcount:30,1,1 +branch:30,nottaken +branch:30,taken +lcount:32,1,0 +branch:32,nottaken +branch:32,taken +lcount:33,0,1 +branch:33,notexec +branch:33,notexec +lcount:35,1,0 +branch:35,taken +branch:35,nottaken +lcount:36,1,0 @end smallexample @item -k @@ -391,79 +422,158 @@ source file compiled with @option{-fprofile-arcs}, an accompanying Running @command{gcov} with your program's source file names as arguments will now produce a listing of the code along with frequency of execution -for each line. For example, if your program is called @file{tmp.c}, this +for each line. For example, if your program is called @file{tmp.cpp}, this is what you see when you use the basic @command{gcov} facility: @smallexample -$ gcc -fprofile-arcs -ftest-coverage tmp.c +$ g++ -fprofile-arcs -ftest-coverage tmp.cpp $ a.out -$ gcov tmp.c -File 'tmp.c' -Lines executed:90.00% of 10 -Creating 'tmp.c.gcov' +$ gcov tmp.cpp -m +File 'tmp.cpp' +Lines executed:92.86% of 14 +Creating 'tmp.cpp.gcov' @end smallexample -The file @file{tmp.c.gcov} contains output from @command{gcov}. +The file @file{tmp.cpp.gcov} contains output from @command{gcov}. Here is a sample: @smallexample - -: 0:Source:tmp.c + -: 0:Source:tmp.cpp -: 0:Graph:tmp.gcno -: 0:Data:tmp.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:#include -: 2: - -: 3:int main (void) - 1: 4:@{ - 1: 5: int i, total; - -: 6: - 1: 7: total = 0; - -: 8: - 11: 9: for (i = 0; i < 10; i++) - 10: 10: total += i; - -: 11: - 1: 12: if (total != 45) - #####: 13: printf ("Failure\n"); - -: 14: else - 1: 15: printf ("Success\n"); - 1: 16: return 0; - -: 17:@} + -: 3:template + -: 4:class Foo + -: 5:@{ + -: 6: public: + 1*: 7: Foo(): b (1000) @{@} +------------------ +Foo::Foo(): + #####: 7: Foo(): b (1000) @{@} +------------------ +Foo::Foo(): + 1: 7: Foo(): b (1000) @{@} +------------------ + 2*: 8: void inc () @{ b++; @} +------------------ +Foo::inc(): + #####: 8: void inc () @{ b++; @} +------------------ +Foo::inc(): + 2: 8: void inc () @{ b++; @} +------------------ + -: 9: + -: 10: private: + -: 11: int b; + -: 12:@}; + -: 13: + -: 14:template class Foo; + -: 15:template class Foo; + -: 16: + -: 17:int + 1: 18:main (void) + -: 19:@{ + -: 20: int i, total; + 1: 21: Foo counter; + -: 22: + 1: 23: counter.inc(); + 1: 24: counter.inc(); + 1: 25: total = 0; + -: 26: + 11: 27: for (i = 0; i < 10; i++) + 10: 28: total += i; + -: 29: + 1*: 30: int v = total > 100 ? 1 : 2; + -: 31: + 1: 32: if (total != 45) + #####: 33: printf ("Failure\n"); + -: 34: else + 1: 35: printf ("Success\n"); + 1: 36: return 0; + -: 37:@} @end smallexample +Note that line 7 is shown in the report multiple times. First occurrence +presents total number of execution of the line and the next two belong +to instances of class Foo constructors. As you can also see, line 30 contains +some unexecuted basic blocks and thus execution count has asterisk symbol. + When you use the @option{-a} option, you will get individual block counts, and the output looks like this: @smallexample - -: 0:Source:tmp.c + -: 0:Source:tmp.cpp -: 0:Graph:tmp.gcno -: 0:Data:tmp.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:#include -: 2: - -: 3:int main (void) - 1: 4:@{ - 1: 4-block 0 - 1: 5: int i, total; - -: 6: - 1: 7: total = 0; - -: 8: - 11: 9: for (i = 0; i < 10; i++) - 11: 9-block 0 - 10: 10: total += i; - 10: 10-block 0 - -: 11: - 1: 12: if (total != 45) - 1: 12-block 0 - #####: 13: printf ("Failure\n"); - $$$$$: 13-block 0 - -: 14: else - 1: 15: printf ("Success\n"); - 1: 15-block 0 - 1: 16: return 0; - 1: 16-block 0 - -: 17:@} + -: 3:template + -: 4:class Foo + -: 5:@{ + -: 6: public: + 1*: 7: Foo(): b (1000) @{@} +------------------ +Foo::Foo(): + #####: 7: Foo(): b (1000) @{@} +------------------ +Foo::Foo(): + 1: 7: Foo(): b (1000) @{@} +------------------ + 2*: 8: void inc () @{ b++; @} +------------------ +Foo::inc(): + #####: 8: void inc () @{ b++; @} +------------------ +Foo::inc(): + 2: 8: void inc () @{ b++; @} +------------------ + -: 9: + -: 10: private: + -: 11: int b; + -: 12:@}; + -: 13: + -: 14:template class Foo; + -: 15:template class Foo; + -: 16: + -: 17:int + 1: 18:main (void) + -: 19:@{ + -: 20: int i, total; + 1: 21: Foo counter; + 1: 21-block 0 + -: 22: + 1: 23: counter.inc(); + 1: 23-block 0 + 1: 24: counter.inc(); + 1: 24-block 0 + 1: 25: total = 0; + -: 26: + 11: 27: for (i = 0; i < 10; i++) + 1: 27-block 0 + 11: 27-block 1 + 10: 28: total += i; + 10: 28-block 0 + -: 29: + 1*: 30: int v = total > 100 ? 1 : 2; + 1: 30-block 0 + %%%%%: 30-block 1 + 1: 30-block 2 + -: 31: + 1: 32: if (total != 45) + 1: 32-block 0 + #####: 33: printf ("Failure\n"); + %%%%%: 33-block 0 + -: 34: else + 1: 35: printf ("Success\n"); + 1: 35-block 0 + 1: 36: return 0; + 1: 36-block 0 + -: 37:@} @end smallexample In this mode, each basic block is only shown on one line -- the last @@ -477,53 +587,94 @@ block, the branch and call counts of the block will be shown, if the Because of the way GCC instruments calls, a call count can be shown after a line with no individual blocks. -As you can see, line 13 contains a basic block that was not executed. +As you can see, line 33 contains a basic block that was not executed. @need 450 When you use the @option{-b} option, your output looks like this: @smallexample -$ gcov -b tmp.c -File 'tmp.c' -Lines executed:90.00% of 10 -Branches executed:80.00% of 5 -Taken at least once:80.00% of 5 -Calls executed:50.00% of 2 -Creating 'tmp.c.gcov' -@end smallexample - -Here is a sample of a resulting @file{tmp.c.gcov} file: - -@smallexample - -: 0:Source:tmp.c + -: 0:Source:tmp.cpp -: 0:Graph:tmp.gcno -: 0:Data:tmp.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:#include -: 2: - -: 3:int main (void) -function main called 1 returned 1 blocks executed 75% - 1: 4:@{ - 1: 5: int i, total; - -: 6: - 1: 7: total = 0; - -: 8: - 11: 9: for (i = 0; i < 10; i++) + -: 3:template + -: 4:class Foo + -: 5:@{ + -: 6: public: + 1*: 7: Foo(): b (1000) @{@} +------------------ +Foo::Foo(): +function Foo::Foo() called 0 returned 0% blocks executed 0% + #####: 7: Foo(): b (1000) @{@} +------------------ +Foo::Foo(): +function Foo::Foo() called 1 returned 100% blocks executed 100% + 1: 7: Foo(): b (1000) @{@} +------------------ + 2*: 8: void inc () @{ b++; @} +------------------ +Foo::inc(): +function Foo::inc() called 0 returned 0% blocks executed 0% + #####: 8: void inc () @{ b++; @} +------------------ +Foo::inc(): +function Foo::inc() called 2 returned 100% blocks executed 100% + 2: 8: void inc () @{ b++; @} +------------------ + -: 9: + -: 10: private: + -: 11: int b; + -: 12:@}; + -: 13: + -: 14:template class Foo; + -: 15:template class Foo; + -: 16: + -: 17:int +function main called 1 returned 100% blocks executed 81% + 1: 18:main (void) + -: 19:@{ + -: 20: int i, total; + 1: 21: Foo counter; +call 0 returned 100% +branch 1 taken 100% (fallthrough) +branch 2 taken 0% (throw) + -: 22: + 1: 23: counter.inc(); +call 0 returned 100% +branch 1 taken 100% (fallthrough) +branch 2 taken 0% (throw) + 1: 24: counter.inc(); +call 0 returned 100% +branch 1 taken 100% (fallthrough) +branch 2 taken 0% (throw) + 1: 25: total = 0; + -: 26: + 11: 27: for (i = 0; i < 10; i++) branch 0 taken 91% (fallthrough) branch 1 taken 9% - 10: 10: total += i; - -: 11: - 1: 12: if (total != 45) + 10: 28: total += i; + -: 29: + 1*: 30: int v = total > 100 ? 1 : 2; +branch 0 taken 0% (fallthrough) +branch 1 taken 100% + -: 31: + 1: 32: if (total != 45) branch 0 taken 0% (fallthrough) branch 1 taken 100% - #####: 13: printf ("Failure\n"); + #####: 33: printf ("Failure\n"); call 0 never executed - -: 14: else - 1: 15: printf ("Success\n"); -call 0 called 1 returned 100% - 1: 16: return 0; - -: 17:@} +branch 1 never executed +branch 2 never executed + -: 34: else + 1: 35: printf ("Success\n"); +call 0 returned 100% +branch 1 taken 100% (fallthrough) +branch 2 taken 0% (throw) + 1: 36: return 0; + -: 37:@} @end smallexample For each function, a line is printed showing how many times the function diff --git a/gcc/gcov-dump.c b/gcc/gcov-dump.c index d24e72ac4a1..c4e05cd4795 100644 --- a/gcc/gcov-dump.c +++ b/gcc/gcov-dump.c @@ -308,9 +308,15 @@ tag_function (const char *filename ATTRIBUTE_UNUSED, name = gcov_read_string (); printf (", `%s'", name ? name : "NULL"); + unsigned artificial = gcov_read_unsigned (); name = gcov_read_string (); printf (" %s", name ? name : "NULL"); - printf (":%u", gcov_read_unsigned ()); + unsigned line_start = gcov_read_unsigned (); + unsigned column_start = gcov_read_unsigned (); + unsigned line_end = gcov_read_unsigned (); + printf (":%u:%u:%u", line_start, column_start, line_end); + if (artificial) + printf (", artificial"); } } } diff --git a/gcc/gcov.c b/gcc/gcov.c index 48bcdc0d4c3..846a2326196 100644 --- a/gcc/gcov.c +++ b/gcc/gcov.c @@ -34,6 +34,8 @@ along with Gcov; see the file COPYING3. If not see #define INCLUDE_ALGORITHM #define INCLUDE_VECTOR #define INCLUDE_STRING +#define INCLUDE_MAP +#define INCLUDE_SET #include "system.h" #include "coretypes.h" #include "tm.h" @@ -183,6 +185,42 @@ block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0), cycle.arc = NULL; } +/* Describes a single line of source. Contains a chain of basic blocks + with code on it. */ + +struct line_info +{ + /* Default constructor. */ + line_info (); + + /* Return true when NEEDLE is one of basic blocks the line belongs to. */ + bool has_block (block_t *needle); + + /* Execution count. */ + gcov_type count; + + /* Branches from blocks that end on this line. */ + vector branches; + + /* blocks which start on this line. Used in all-blocks mode. */ + vector blocks; + + unsigned exists : 1; + unsigned unexceptional : 1; + unsigned has_unexecuted_block : 1; +}; + +line_info::line_info (): count (0), branches (), blocks (), exists (false), + unexceptional (0), has_unexecuted_block (0) +{ +} + +bool +line_info::has_block (block_t *needle) +{ + return std::find (blocks.begin (), blocks.end (), needle) != blocks.end (); +} + /* Describes a single function. Contains an array of basic blocks. */ typedef struct function_info @@ -190,6 +228,10 @@ typedef struct function_info function_info (); ~function_info (); + /* Return true when line N belongs to the function in source file SRC_IDX. + The line must be defined in body of the function, can't be inlined. */ + bool group_line_p (unsigned n, unsigned src_idx); + /* Name of function. */ char *name; char *demangled_name; @@ -200,6 +242,13 @@ typedef struct function_info /* The graph contains at least one fake incoming edge. */ unsigned has_catch : 1; + /* True when the function is artificial and does not exist + in a source file. */ + unsigned artificial : 1; + + /* True when multiple functions start at a line in a source file. */ + unsigned is_group : 1; + /* Array of basic blocks. Like in GCC, the entry block is at blocks[0] and the exit block is at blocks[1]. */ #define ENTRY_BLOCK (0) @@ -211,17 +260,39 @@ typedef struct function_info gcov_type *counts; unsigned num_counts; - /* First line number & file. */ - unsigned line; + /* First line number. */ + unsigned start_line; + + /* First line column. */ + unsigned start_column; + + /* Last line number. */ + unsigned end_line; + + /* Index of source file where the function is defined. */ unsigned src; - /* Next function in same source file. */ - struct function_info *next_file_fn; + /* Vector of line information. */ + vector lines; /* Next function. */ struct function_info *next; } function_t; +/* Function info comparer that will sort functions according to starting + line. */ + +struct function_line_start_cmp +{ + inline bool operator() (const function_info *lhs, + const function_info *rhs) + { + return (lhs->start_line == rhs->start_line + ? lhs->start_column < rhs->start_column + : lhs->start_line < rhs->start_line); + } +}; + /* Describes coverage of a file or function. */ typedef struct coverage_info @@ -239,42 +310,6 @@ typedef struct coverage_info char *name; } coverage_t; -/* Describes a single line of source. Contains a chain of basic blocks - with code on it. */ - -struct line_info -{ - /* Default constructor. */ - line_info (); - - /* Return true when NEEDLE is one of basic blocks the line belongs to. */ - bool has_block (block_t *needle); - - /* Execution count. */ - gcov_type count; - - /* Branches from blocks that end on this line. */ - vector branches; - - /* blocks which start on this line. Used in all-blocks mode. */ - vector blocks; - - unsigned exists : 1; - unsigned unexceptional : 1; - unsigned has_unexecuted_block : 1; -}; - -line_info::line_info (): count (0), branches (), blocks (), exists (false), - unexceptional (0), has_unexecuted_block (0) -{ -} - -bool -line_info::has_block (block_t *needle) -{ - return std::find (blocks.begin (), blocks.end (), needle) != blocks.end (); -} - /* Describes a file mentioned in the block graph. Contains an array of line info. */ @@ -283,6 +318,11 @@ struct source_info /* Default constructor. */ source_info (); + vector get_functions_at_location (unsigned line_num) const; + + /* Index of the source_info in sources vector. */ + unsigned index; + /* Canonical name of source file. */ char *name; time_t file_time; @@ -294,14 +334,31 @@ struct source_info /* Functions in this source file. These are in ascending line number order. */ - function_t *functions; + vector functions; }; -source_info::source_info (): name (NULL), file_time (), lines (), - coverage (), functions (NULL) +source_info::source_info (): index (0), name (NULL), file_time (), + lines (), coverage (), functions () { } +vector +source_info::get_functions_at_location (unsigned line_num) const +{ + vector r; + + for (vector::const_iterator it = functions.begin (); + it != functions.end (); it++) + { + if ((*it)->start_line == line_num && (*it)->src == index) + r.push_back (*it); + } + + std::sort (r.begin (), r.end (), function_line_start_cmp ()); + + return r; +} + class name_map { public: @@ -495,8 +552,9 @@ extern int main (int, char **); function_info::function_info (): name (NULL), demangled_name (NULL), ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0), + artificial (0), is_group (0), blocks (), blocks_executed (0), counts (NULL), num_counts (0), - line (0), src (0), next_file_fn (NULL), next (NULL) + start_line (0), start_column (0), end_line (0), src (0), lines (), next (NULL) { } @@ -518,6 +576,11 @@ function_info::~function_info () free (name); } +bool function_info::group_line_p (unsigned n, unsigned src_idx) +{ + return is_group && src == src_idx && start_line <= n && n <= end_line; +} + /* Cycle detection! There are a bajillion algorithms that do this. Boost's function is named hawick_cycles, so I used the algorithm by K. A. Hawick and H. A. James in @@ -889,6 +952,42 @@ process_args (int argc, char **argv) return optind; } +/* Output intermediate LINE sitting on LINE_NUM to output file F. */ + +static void +output_intermediate_line (FILE *f, line_info *line, unsigned line_num) +{ + if (!line->exists) + return; + + fprintf (f, "lcount:%u,%s,%d\n", line_num, + format_gcov (line->count, 0, -1), + line->has_unexecuted_block); + + vector::const_iterator it; + if (flag_branches) + for (it = line->branches.begin (); it != line->branches.end (); + it++) + { + if (!(*it)->is_unconditional && !(*it)->is_call_non_return) + { + const char *branch_type; + /* branch:, + branch_coverage_type + : notexec (Branch not executed) + : taken (Branch executed and taken) + : nottaken (Branch executed, but not taken) + */ + if ((*it)->src->count) + branch_type + = ((*it)->count > 0) ? "taken" : "nottaken"; + else + branch_type = "notexec"; + fprintf (f, "branch:%d,%s\n", line_num, branch_type); + } + } +} + /* Output the result in intermediate format used by 'lcov'. The intermediate format contains a single file named 'foo.cc.gcov', @@ -902,50 +1001,95 @@ file 'foo.cc.gcov' similar to the above example. */ static void output_intermediate_file (FILE *gcov_file, source_info *src) { - unsigned line_num; /* current line number. */ - const line_info *line; /* current line info ptr. */ - function_t *fn; /* current function info ptr. */ - fprintf (gcov_file, "file:%s\n", src->name); /* source file name */ - for (fn = src->functions; fn; fn = fn->next_file_fn) + std::sort (src->functions.begin (), src->functions.end (), + function_line_start_cmp ()); + for (vector::iterator it = src->functions.begin (); + it != src->functions.end (); it++) { /* function:,, */ - fprintf (gcov_file, "function:%d,%s,%s\n", fn->line, - format_gcov (fn->blocks[0].count, 0, -1), - flag_demangled_names ? fn->demangled_name : fn->name); + fprintf (gcov_file, "function:%d,%d,%s,%s\n", (*it)->start_line, + (*it)->end_line, format_gcov ((*it)->blocks[0].count, 0, -1), + flag_demangled_names ? (*it)->demangled_name : (*it)->name); } - for (line_num = 1, line = &src->lines[line_num]; - line_num < src->lines.size (); - line_num++, line++) + for (unsigned line_num = 0; line_num <= src->lines.size (); line_num++) { - if (line->exists) - fprintf (gcov_file, "lcount:%u,%s,%d\n", line_num, - format_gcov (line->count, 0, -1), line->has_unexecuted_block); - if (flag_branches) - for (vector::const_iterator it = line->branches.begin (); - it != line->branches.end (); it++) - { - if (!(*it)->is_unconditional && !(*it)->is_call_non_return) - { - const char *branch_type; - /* branch:, - branch_coverage_type - : notexec (Branch not executed) - : taken (Branch executed and taken) - : nottaken (Branch executed, but not taken) - */ - if ((*it)->src->count) - branch_type = ((*it)->count > 0) ? "taken" : "nottaken"; - else - branch_type = "notexec"; - fprintf (gcov_file, "branch:%d,%s\n", line_num, branch_type); - } - } + vector fns = src->get_functions_at_location (line_num); + + /* Print first group functions that begin on the line. */ + for (vector::iterator it2 = fns.begin (); + it2 != fns.end (); it2++) + { + vector &lines = (*it2)->lines; + for (unsigned i = 0; i < lines.size (); i++) + { + line_info *line = &lines[i]; + output_intermediate_line (gcov_file, line, line_num + i); + } + } + + /* Follow with lines associated with the source file. */ + output_intermediate_line (gcov_file, &src->lines[line_num], line_num); } } +/* Function start pair. */ +struct function_start +{ + unsigned source_file_idx; + unsigned start_line; +}; + +/* Traits class for function start hash maps below. */ + +struct function_start_pair_hash : typed_noop_remove +{ + typedef function_start value_type; + typedef function_start compare_type; + + static hashval_t + hash (const function_start &ref) + { + inchash::hash hstate (0); + hstate.add_int (ref.source_file_idx); + hstate.add_int (ref.start_line); + return hstate.end (); + } + + static bool + equal (const function_start &ref1, const function_start &ref2) + { + return (ref1.source_file_idx == ref2.source_file_idx + && ref1.start_line == ref2.start_line); + } + + static void + mark_deleted (function_start &ref) + { + ref.start_line = ~1U; + } + + static void + mark_empty (function_start &ref) + { + ref.start_line = ~2U; + } + + static bool + is_deleted (const function_start &ref) + { + return ref.start_line == ~1U; + } + + static bool + is_empty (const function_start &ref) + { + return ref.start_line == ~2U; + } +}; + /* Process a single input file. */ static void @@ -959,6 +1103,28 @@ process_file (const char *file_name) return; read_count_file (fns); + + hash_map fn_map; + + /* Identify group functions. */ + for (function_t *f = fns; f; f = f->next) + if (!f->artificial) + { + function_start needle; + needle.source_file_idx = f->src; + needle.start_line = f->start_line; + + function_t **slot = fn_map.get (needle); + if (slot) + { + gcc_assert ((*slot)->end_line == f->end_line); + (*slot)->is_group = 1; + f->is_group = 1; + } + else + fn_map.put (needle, f); + } + while (fns) { function_t *fn = fns; @@ -968,46 +1134,50 @@ process_file (const char *file_name) if (fn->counts || no_data_file) { unsigned src = fn->src; - unsigned line = fn->line; unsigned block_no; - function_t *probe, **prev; - - /* Now insert it into the source file's list of - functions. Normally functions will be encountered in - ascending order, so a simple scan is quick. Note we're - building this list in reverse order. */ - for (prev = &sources[src].functions; - (probe = *prev); prev = &probe->next_file_fn) - if (probe->line <= line) - break; - fn->next_file_fn = probe; - *prev = fn; - /* Mark last line in files touched by function. */ - for (block_no = 0; block_no != fn->blocks.size (); block_no++) + /* Process only non-artificial functions. */ + if (!fn->artificial) { - block_t *block = &fn->blocks[block_no]; - for (unsigned i = 0; i < block->locations.size (); i++) - { - unsigned s = block->locations[i].source_file_idx; - - /* Sort lines of locations. */ - sort (block->locations[i].lines.begin (), - block->locations[i].lines.end ()); + source_info *s = &sources[src]; + s->functions.push_back (fn); - if (!block->locations[i].lines.empty ()) + /* Mark last line in files touched by function. */ + for (block_no = 0; block_no != fn->blocks.size (); block_no++) + { + block_t *block = &fn->blocks[block_no]; + for (unsigned i = 0; i < block->locations.size (); i++) { - unsigned last_line - = block->locations[i].lines.back () + 1; - if (last_line > sources[s].lines.size ()) - sources[s].lines.resize (last_line); + /* Sort lines of locations. */ + sort (block->locations[i].lines.begin (), + block->locations[i].lines.end ()); + + if (!block->locations[i].lines.empty ()) + { + s = &sources[block->locations[i].source_file_idx]; + unsigned last_line + = block->locations[i].lines.back (); + + /* Record new lines for the function. */ + if (last_line >= s->lines.size ()) + { + /* Record new lines for a source file. */ + s->lines.resize (last_line + 1); + } + } } + + /* Allocate lines for group function, following start_line + and end_line information of the function. */ + if (fn->is_group) + fn->lines.resize (fn->end_line - fn->start_line + 1); } + + solve_flow_graph (fn); + if (fn->has_catch) + find_exception_blocks (fn); } - solve_flow_graph (fn); - if (fn->has_catch) - find_exception_blocks (fn); *fn_end = fn; fn_end = &fn->next; } @@ -1057,6 +1227,8 @@ generate_results (const char *file_name) for (fn = functions; fn; fn = fn->next) { coverage_t coverage; + if (fn->artificial) + continue; memset (&coverage, 0, sizeof (coverage)); coverage.name = flag_demangled_names ? fn->demangled_name : fn->name; @@ -1237,6 +1409,7 @@ find_source (const char *file_name) src = &sources.back (); src->name = canon; src->coverage.name = src->name; + src->index = idx; if (source_length #if HAVE_DOS_BASED_FILE_SYSTEM /* You lose if separators don't match exactly in the @@ -1328,15 +1501,18 @@ read_graph_file (void) if (tag == GCOV_TAG_FUNCTION) { char *function_name; - unsigned ident, lineno; + unsigned ident; unsigned lineno_checksum, cfg_checksum; ident = gcov_read_unsigned (); lineno_checksum = gcov_read_unsigned (); cfg_checksum = gcov_read_unsigned (); function_name = xstrdup (gcov_read_string ()); + unsigned artificial = gcov_read_unsigned (); unsigned src_idx = find_source (gcov_read_string ()); - lineno = gcov_read_unsigned (); + unsigned start_line = gcov_read_unsigned (); + unsigned start_column = gcov_read_unsigned (); + unsigned end_line = gcov_read_unsigned (); fn = new function_t; fn->name = function_name; @@ -1350,9 +1526,11 @@ read_graph_file (void) fn->lineno_checksum = lineno_checksum; fn->cfg_checksum = cfg_checksum; fn->src = src_idx; - fn->line = lineno; + fn->start_line = start_line; + fn->start_column = start_column; + fn->end_line = end_line; + fn->artificial = artificial; - fn->next_file_fn = NULL; fn->next = NULL; *fns_end = fn; fns_end = &fn->next; @@ -2266,48 +2444,66 @@ add_line_counts (coverage_t *coverage, function_t *fn) fn->blocks_executed++; for (unsigned i = 0; i < block->locations.size (); i++) { - source_info *src = &sources[block->locations[i].source_file_idx]; - + unsigned src_idx = block->locations[i].source_file_idx; vector &lines = block->locations[i].lines; + + block->cycle.arc = NULL; + block->cycle.ident = ~0U; + for (unsigned j = 0; j < lines.size (); j++) { - line = &src->lines[lines[j]]; - if (coverage) + unsigned ln = lines[j]; + + /* Line belongs to a function that is in a group. */ + if (fn->group_line_p (ln, src_idx)) { - if (!line->exists) - coverage->lines++; - if (!line->count && block->count) - coverage->lines_executed++; + gcc_assert (lines[j] - fn->start_line < fn->lines.size ()); + line = &(fn->lines[lines[j] - fn->start_line]); + line->exists = 1; + if (!block->exceptional) + { + line->unexceptional = 1; + if (block->count == 0) + line->has_unexecuted_block = 1; + } + line->count += block->count; } - line->exists = 1; - if (!block->exceptional) + else { - line->unexceptional = 1; - if (block->count == 0) - line->has_unexecuted_block = 1; + gcc_assert (ln < sources[src_idx].lines.size ()); + line = &(sources[src_idx].lines[ln]); + if (coverage) + { + if (!line->exists) + coverage->lines++; + if (!line->count && block->count) + coverage->lines_executed++; + } + line->exists = 1; + if (!block->exceptional) + { + line->unexceptional = 1; + if (block->count == 0) + line->has_unexecuted_block = 1; + } + line->count += block->count; } - line->count += block->count; } - } - block->cycle.arc = NULL; - block->cycle.ident = ~0U; - has_any_line = true; - if (!ix || ix + 1 == fn->blocks.size ()) - /* Entry or exit block */; - else if (line != NULL) - { - line->blocks.push_back (block); + has_any_line = true; - if (flag_branches) + if (!ix || ix + 1 == fn->blocks.size ()) + /* Entry or exit block. */; + else if (line != NULL) { - arc_t *arc; + line->blocks.push_back (block); - for (arc = block->succ; arc; arc = arc->succ_next) + if (flag_branches) { - line->branches.push_back (arc); - if (coverage && !arc->is_unconditional) - add_branch_counts (coverage, arc); + arc_t *arc; + + for (arc = block->succ; arc; arc = arc->succ_next) + line->branches.push_back (arc); } } } @@ -2317,72 +2513,113 @@ add_line_counts (coverage_t *coverage, function_t *fn) fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name); } +/* Accumulate info for LINE that belongs to SRC source file. If ADD_COVERAGE + is set to true, update source file summary. */ + +static void accumulate_line_info (line_info *line, source_info *src, + bool add_coverage) +{ + if (add_coverage) + for (vector::iterator it = line->branches.begin (); + it != line->branches.end (); it++) + add_branch_counts (&src->coverage, *it); + + if (!line->blocks.empty ()) + { + /* The user expects the line count to be the number of times + a line has been executed. Simply summing the block count + will give an artificially high number. The Right Thing + is to sum the entry counts to the graph of blocks on this + line, then find the elementary cycles of the local graph + and add the transition counts of those cycles. */ + gcov_type count = 0; + + /* Cycle detection. */ + for (vector::iterator it = line->blocks.begin (); + it != line->blocks.end (); it++) + { + for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next) + if (!line->has_block (arc->src)) + count += arc->count; + for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next) + arc->cs_count = arc->count; + } + + /* Now, add the count of loops entirely on this line. */ + count += get_cycles_count (*line); + line->count = count; + } + + if (line->exists && add_coverage) + { + src->coverage.lines++; + if (line->count) + src->coverage.lines_executed++; + } +} + /* Accumulate the line counts of a file. */ static void accumulate_line_counts (source_info *src) { - function_t *fn, *fn_p, *fn_n; - unsigned ix = 0; - - /* Reverse the function order. */ - for (fn = src->functions, fn_p = NULL; fn; fn_p = fn, fn = fn_n) + /* First work on group functions. */ + for (vector::iterator it = src->functions.begin (); + it != src->functions.end (); it++) { - fn_n = fn->next_file_fn; - fn->next_file_fn = fn_p; + function_info *fn = *it; + + if (fn->src != src->index || !fn->is_group) + continue; + + for (vector::iterator it2 = fn->lines.begin (); + it2 != fn->lines.end (); it2++) + { + line_info *line = &(*it2); + accumulate_line_info (line, src, false); + } } - src->functions = fn_p; - for (vector::reverse_iterator it = src->lines.rbegin (); - it != src->lines.rend (); it++) - { - line_info *line = &(*it); - if (!line->blocks.empty ()) - { - /* The user expects the line count to be the number of times - a line has been executed. Simply summing the block count - will give an artificially high number. The Right Thing - is to sum the entry counts to the graph of blocks on this - line, then find the elementary cycles of the local graph - and add the transition counts of those cycles. */ - gcov_type count = 0; - - /* Sum the entry arcs. */ - for (vector::iterator it = line->blocks.begin (); - it != line->blocks.end (); it++) - { - arc_t *arc; + /* Work on global lines that line in source file SRC. */ + for (vector::iterator it = src->lines.begin (); + it != src->lines.end (); it++) + accumulate_line_info (&(*it), src, true); - for (arc = (*it)->pred; arc; arc = arc->pred_next) - if (flag_branches) - add_branch_counts (&src->coverage, arc); - } + /* If not using intermediate mode, sum lines of group functions and + add them to lines that live in a source file. */ + if (!flag_intermediate_format) + for (vector::iterator it = src->functions.begin (); + it != src->functions.end (); it++) + { + function_info *fn = *it; - /* Cycle detection. */ - for (vector::iterator it = line->blocks.begin (); - it != line->blocks.end (); it++) - { - for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next) - if (!line->has_block (arc->src)) - count += arc->count; - for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next) - arc->cs_count = arc->count; - } + if (fn->src != src->index || !fn->is_group) + continue; - /* Now, add the count of loops entirely on this line. */ - count += get_cycles_count (*line); - line->count = count; - } + for (unsigned i = 0; i < fn->lines.size (); i++) + { + line_info *fn_line = &fn->lines[i]; + if (fn_line->exists) + { + unsigned ln = fn->start_line + i; + line_info *src_line = &src->lines[ln]; - if (line->exists) - { - src->coverage.lines++; - if (line->count) - src->coverage.lines_executed++; - } + if (!src_line->exists) + src->coverage.lines++; + if (!src_line->count && fn_line->count) + src->coverage.lines_executed++; - ix++; - } + src_line->count += fn_line->count; + src_line->exists = 1; + + if (fn_line->has_unexecuted_block) + src_line->has_unexecuted_block = 1; + + if (fn_line->unexceptional) + src_line->unexceptional = 1; + } + } + } } /* Output information about ARC number IX. Returns nonzero if @@ -2500,7 +2737,8 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional, if (flag_use_colors) { pad_count_string (s); - s = SGR_SEQ (COLOR_BG_MAGENTA COLOR_SEPARATOR COLOR_FG_WHITE); + s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA + COLOR_SEPARATOR COLOR_FG_WHITE)); s += SGR_RESET; } else @@ -2538,6 +2776,86 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional, fprintf (f, "%s:%5u", s.c_str (), line_num); } +static void +print_source_line (FILE *f, const vector &source_lines, + unsigned line) +{ + gcc_assert (line >= 1); + gcc_assert (line <= source_lines.size ()); + + fprintf (f, ":%s\n", source_lines[line - 1]); +} + +/* Output line details for LINE and print it to F file. LINE lives on + LINE_NUM. */ + +static void +output_line_details (FILE *f, const line_info *line, unsigned line_num) +{ + if (flag_all_blocks) + { + arc_t *arc; + int ix, jx; + + ix = jx = 0; + for (vector::const_iterator it = line->blocks.begin (); + it != line->blocks.end (); it++) + { + if (!(*it)->is_call_return) + { + output_line_beginning (f, line->exists, + (*it)->exceptional, false, + (*it)->count, line_num, + "%%%%%", "$$$$$"); + fprintf (f, "-block %2d", ix++); + if (flag_verbose) + fprintf (f, " (BB %u)", (*it)->id); + fprintf (f, "\n"); + } + if (flag_branches) + for (arc = (*it)->succ; arc; arc = arc->succ_next) + jx += output_branch_count (f, jx, arc); + } + } + else if (flag_branches) + { + int ix; + + ix = 0; + for (vector::const_iterator it = line->branches.begin (); + it != line->branches.end (); it++) + ix += output_branch_count (f, ix, (*it)); + } +} + +/* Output detail statistics about function FN to file F. */ + +static void +output_function_details (FILE *f, const function_info *fn) +{ + if (!flag_branches) + return; + + arc_t *arc = fn->blocks[EXIT_BLOCK].pred; + gcov_type return_count = fn->blocks[EXIT_BLOCK].count; + gcov_type called_count = fn->blocks[ENTRY_BLOCK].count; + + for (; arc; arc = arc->pred_next) + if (arc->fake) + return_count -= arc->count; + + fprintf (f, "function %s", + flag_demangled_names ? fn->demangled_name : fn->name); + fprintf (f, " called %s", + format_gcov (called_count, 0, -1)); + fprintf (f, " returned %s", + format_gcov (return_count, called_count, 0)); + fprintf (f, " blocks executed %s", + format_gcov (fn->blocks_executed, fn->blocks.size () - 2, + 0)); + fprintf (f, "\n"); +} + /* Read in the source file one line at a time, and output that line to the gcov file preceded by its execution count and other information. */ @@ -2546,12 +2864,10 @@ static void output_lines (FILE *gcov_file, const source_info *src) { #define DEFAULT_LINE_START " -: 0:" +#define FN_SEPARATOR "------------------\n" FILE *source_file; - unsigned line_num; /* current line number. */ - const line_info *line; /* current line info ptr. */ - const char *retval = ""; /* status of source file reading. */ - function_t *fn = NULL; + const char *retval; fprintf (gcov_file, DEFAULT_LINE_START "Source:%s\n", src->coverage.name); if (!multiple_files) @@ -2565,43 +2881,40 @@ output_lines (FILE *gcov_file, const source_info *src) source_file = fopen (src->name, "r"); if (!source_file) - { - fnotice (stderr, "Cannot open source file %s\n", src->name); - retval = NULL; - } + fnotice (stderr, "Cannot open source file %s\n", src->name); else if (src->file_time == 0) fprintf (gcov_file, DEFAULT_LINE_START "Source is newer than graph\n"); - if (flag_branches) - fn = src->functions; + vector source_lines; + if (source_file) + while ((retval = read_line (source_file)) != NULL) + source_lines.push_back (xstrdup (retval)); - for (line_num = 1, line = &src->lines[line_num]; - line_num < src->lines.size (); line_num++, line++) + unsigned line_start_group = 0; + vector fns; + + for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++) { - for (; fn && fn->line == line_num; fn = fn->next_file_fn) + if (line_num >= src->lines.size ()) { - arc_t *arc = fn->blocks[EXIT_BLOCK].pred; - gcov_type return_count = fn->blocks[EXIT_BLOCK].count; - gcov_type called_count = fn->blocks[ENTRY_BLOCK].count; - - for (; arc; arc = arc->pred_next) - if (arc->fake) - return_count -= arc->count; - - fprintf (gcov_file, "function %s", flag_demangled_names ? - fn->demangled_name : fn->name); - fprintf (gcov_file, " called %s", - format_gcov (called_count, 0, -1)); - fprintf (gcov_file, " returned %s", - format_gcov (return_count, called_count, 0)); - fprintf (gcov_file, " blocks executed %s", - format_gcov (fn->blocks_executed, fn->blocks.size () - 2, - 0)); - fprintf (gcov_file, "\n"); + fprintf (gcov_file, "%9s:%5u", "-", line_num); + print_source_line (gcov_file, source_lines, line_num); + continue; } - if (retval) - retval = read_line (source_file); + const line_info *line = &src->lines[line_num]; + + if (line_start_group == 0) + { + fns = src->get_functions_at_location (line_num); + if (fns.size () > 1) + line_start_group = fns[0]->end_line; + else if (fns.size () == 1) + { + function_t *fn = fns[0]; + output_function_details (gcov_file, fn); + } + } /* For lines which don't exist in the .bb file, print '-' before the source line. For lines which exist but were never @@ -2610,54 +2923,64 @@ output_lines (FILE *gcov_file, const source_info *src) There are 16 spaces of indentation added before the source line so that tabs won't be messed up. */ output_line_beginning (gcov_file, line->exists, line->unexceptional, - line->has_unexecuted_block, line->count, line_num, - "=====", "#####"); - fprintf (gcov_file, ":%s\n", retval ? retval : "/*EOF*/"); + line->has_unexecuted_block, line->count, + line_num, "=====", "#####"); - if (flag_all_blocks) - { - arc_t *arc; - int ix, jx; + print_source_line (gcov_file, source_lines, line_num); + output_line_details (gcov_file, line, line_num); - ix = jx = 0; - for (vector::const_iterator it = line->blocks.begin (); - it != line->blocks.end (); it++) + if (line_start_group == line_num) + { + for (vector::iterator it = fns.begin (); + it != fns.end (); it++) { - if (!(*it)->is_call_return) + function_info *fn = *it; + vector &lines = fn->lines; + + fprintf (gcov_file, FN_SEPARATOR); + + string fn_name + = flag_demangled_names ? fn->demangled_name : fn->name; + + if (flag_use_colors) { + fn_name.insert (0, SGR_SEQ (COLOR_FG_CYAN)); + fn_name += SGR_RESET; + } + + fprintf (gcov_file, "%s:\n", fn_name.c_str ()); + + output_function_details (gcov_file, fn); + + /* Print all lines covered by the function. */ + for (unsigned i = 0; i < lines.size (); i++) + { + line_info *line = &lines[i]; + unsigned l = fn->start_line + i; + + /* For lines which don't exist in the .bb file, print '-' + before the source line. For lines which exist but + were never executed, print '#####' or '=====' before + the source line. Otherwise, print the execution count + before the source line. + There are 16 spaces of indentation added before the source + line so that tabs won't be messed up. */ output_line_beginning (gcov_file, line->exists, - (*it)->exceptional, false, - (*it)->count, line_num, - "%%%%%", "$$$$$"); - fprintf (gcov_file, "-block %2d", ix++); - if (flag_verbose) - fprintf (gcov_file, " (BB %u)", (*it)->id); - fprintf (gcov_file, "\n"); + line->unexceptional, + line->has_unexecuted_block, + line->count, + l, "=====", "#####"); + + print_source_line (gcov_file, source_lines, l); + output_line_details (gcov_file, line, l); } - if (flag_branches) - for (arc = (*it)->succ; arc; arc = arc->succ_next) - jx += output_branch_count (gcov_file, jx, arc); } - } - else if (flag_branches) - { - int ix; - ix = 0; - for (vector::const_iterator it = line->branches.begin (); - it != line->branches.end (); it++) - ix += output_branch_count (gcov_file, ix, (*it)); + fprintf (gcov_file, FN_SEPARATOR); + line_start_group = 0; } } - /* Handle all remaining source lines. There may be lines after the - last line of code. */ - if (retval) - { - for (; (retval = read_line (source_file)); line_num++) - fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval); - } - if (source_file) fclose (source_file); } -- 2.30.2