From 3112c36a1b78930ec37b5bcd140bb1b1d361c2c6 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 4 Aug 2002 17:06:10 +0000 Subject: [PATCH] * gcov.c (bb_file_time): New static variable. (object_directory): May also be object file. (preserve_paths): New static variable. (print_usage): Adjust. (options): Adjust. (process_args): Adjust. (open_files): Simplify. Cope when OBJECT_DIRECTORY is an object file. Find modification date on bb file. (read_profile): Don't rewind a NULL file. (format_hwint): New static function. (function_summary): Use format_hwint. (output_data): SOURCE_FILE_NAME is never relative to OBJECT_DIRECTORY. Use format_hwint. Adjust gcov file name mangling. Adjust output format to make it more machine readable. * doc/gcov.texi: Document & clarify semantics. git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@56028 138bc75d-0d04-0410-961f-82ee72b054a4 --- gcc/ChangeLog | 18 ++ gcc/doc/gcov.texi | 188 +++++++++++------- gcc/gcov.c | 559 +++++++++++++++++++++++++++++------------------------- 3 files changed, 441 insertions(+), 324 deletions(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 0ea81d1f89f..766d1bc5149 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,21 @@ +2002-08-04 Nathan Sidwell + + * gcov.c (bb_file_time): New static variable. + (object_directory): May also be object file. + (preserve_paths): New static variable. + (print_usage): Adjust. + (options): Adjust. + (process_args): Adjust. + (open_files): Simplify. Cope when OBJECT_DIRECTORY is an object + file. Find modification date on bb file. + (read_profile): Don't rewind a NULL file. + (format_hwint): New static function. + (function_summary): Use format_hwint. + (output_data): SOURCE_FILE_NAME is never relative to + OBJECT_DIRECTORY. Use format_hwint. Adjust gcov file name + mangling. Adjust output format to make it more machine readable. + * doc/gcov.texi: Document & clarify semantics. + 2002-08-04 Joseph S. Myers * doc/include/gcc-common.texi (version-GCC): Increase to 3.3. diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi index 6b0fd82d852..070a08cada7 100644 --- a/gcc/doc/gcov.texi +++ b/gcc/doc/gcov.texi @@ -1,10 +1,11 @@ -@c Copyright (C) 1996, 1997, 1999, 2000, 2001 Free Software Foundation, Inc. +@c Copyright (C) 1996, 1997, 1999, 2000, 2001, +@c 2002 Free Software Foundation, Inc. @c This is part of the GCC manual. @c For copying conditions, see the file gcc.texi. @ignore @c man begin COPYRIGHT -Copyright @copyright{} 1996, 1997, 1999, 2000, 2001 Free Software Foundation, Inc. +Copyright @copyright{} 1996, 1997, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or @@ -47,12 +48,13 @@ test code coverage in your programs. @c man begin DESCRIPTION @command{gcov} is a test coverage program. Use it in concert with GCC -to analyze your programs to help create more efficient, faster -running code. You can use @command{gcov} as a profiling tool to help -discover where your optimization efforts will best affect your code. You -can also use @command{gcov} along with the other profiling tool, -@command{gprof}, to assess which parts of your code use the greatest amount -of computing time. +to analyze your programs to help create more efficient, faster running +code and to discover untested parts of your program. You can use +@command{gcov} as a profiling tool to help discover where your +optimization efforts will best affect your code. You can also use +@command{gcov} along with the other profiling tool, @command{gprof}, to +assess which parts of your code use the greatest amount of computing +time. Profiling tools help you analyze your code's performance. Using a profiler such as @command{gcov} or @command{gprof}, you can find out some @@ -117,10 +119,13 @@ gcov @r{[}@var{options}@r{]} @var{sourcefile} @ignore @c man begin SYNOPSIS gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}] - [@option{-b}|@option{--branch-probabilities}] [@option{-c}|@option{--branch-counts}] - [@option{-n}|@option{--no-output}] [@option{-l}|@option{--long-file-names}] + [@option{-b}|@option{--branch-probabilities}] + [@option{-c}|@option{--branch-counts}] + [@option{-n}|@option{--no-output}] + [@option{-l}|@option{--long-file-names}] + [@option{-p}|@option{--preserve-paths}] [@option{-f}|@option{--function-summaries}] - [@option{-o}|@option{--object-directory} @var{directory}] @var{sourcefile} + [@option{-o}|@option{--object-directory} @var{directory|file}] @var{sourcefile} @c man end @c man begin SEEALSO gpl(7), gfdl(7), fsf-funding(7), gcc(1) and the Info entry for @file{gcc}. @@ -159,31 +164,70 @@ Do not create the @command{gcov} output file. Create long file names for included source files. For example, if the header file @file{x.h} contains code, and was included in the file @file{a.c}, then running @command{gcov} on the file @file{a.c} will produce -an output file called @file{a.c.x.h.gcov} instead of @file{x.h.gcov}. +an output file called @file{a.c##x.h.gcov} instead of @file{x.h.gcov}. This can be useful if @file{x.h} is included in multiple source files. +@item -p +@itemx --preserve-paths +Preserve complete path information in the names of generated +@file{.gcov} files. Without this option, just the filename component is +used. With this option, all directories are used, with '/' characters +translated to '#' characters, '.' directory components removed and '..' +components renamed to '^'. This is useful if sourcefiles are in several +different directories. It also affects the @samp{-l} option. + @item -f @itemx --function-summaries Output summaries for each function in addition to the file level summary. -@item -o @var{directory} +@item -o @var{directory|file} @itemx --object-directory @var{directory} -The directory where the object files live. Gcov will search for @file{.bb}, -@file{.bbg}, and @file{.da} files in this directory. +@itemx --object-file @var{file} +Specify either the directory containing the gcov data files, or the +object path name. The @file{.bb}, @file{.bbg}, and +@file{.da} data files are searched for using this option. If a directory +is specified, the data files are in that directory and named after the +source file name, without its extension. If a file is specified here, +the data files are named after that file, without its extension. If this +option is not supplied, it defaults to the current directory. + @end table -@need 3000 +Gcov should be run with the current directory the same as that when you +invoked the compiler. Otherwise it will not be able to locate the source +files. Gcov produces files called @file{@var{mangledname}.gcov} in the +current directory. These contain the coverage information of the source +file they correspond to. One @file{.gcov} file is produced for each +source file containing code, which was compiled to produce the data +files. The @file{.gcov} files contain the ':' separated fields along +with program source code. The format is + +@smallexample +@var{execution_count}:@var{line_number}:@var{source line text} +@end smallexample + +Additional block information may succeed each line, when requested by +command line option. The @var{execution_count} is @samp{-} for lines +containing no code and @samp{#####} for lines which were never +executed. Some lines of information at the start have @var{line_number} +of zero. + +When printing percentages, 0% and 100% are only printed when the values +are @emph{exactly} 0% and 100% respectively. Other values which would +conventionally be rounded to 0% or 100% are instead printed as the +nearest non-boundary value. + When using @command{gcov}, you must first compile your program with two special GCC options: @samp{-fprofile-arcs -ftest-coverage}. This tells the compiler to generate additional information needed by gcov (basically a flow graph of the program) and also includes additional code in the object files for generating the extra profiling information needed by gcov. These additional files are placed in the -directory where the source code is located. +directory where the object file is located. Running the program will cause profile output to be generated. For each source file compiled with @option{-fprofile-arcs}, an accompanying @file{.da} -file will be placed in the source directory. +file will be placed in the object file directory. 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 @@ -194,7 +238,7 @@ is what you see when you use the basic @command{gcov} facility: $ gcc -fprofile-arcs -ftest-coverage tmp.c $ a.out $ gcov tmp.c - 87.50% of 8 source lines executed in file tmp.c +90.00% of 10 source lines executed in file tmp.c Creating tmp.c.gcov. @end smallexample @@ -202,20 +246,25 @@ The file @file{tmp.c.gcov} contains output from @command{gcov}. Here is a sample: @smallexample - main() - @{ - 1 int i, total; - - 1 total = 0; - - 11 for (i = 0; i < 10; i++) - 10 total += i; - - 1 if (total != 45) - ###### printf ("Failure\n"); - else - 1 printf ("Success\n"); - 1 @} + -: 0:Source:tmp.c + -: 0:Object:tmp.bb + -: 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; + 1: 17:@} @end smallexample @need 450 @@ -223,37 +272,42 @@ When you use the @option{-b} option, your output looks like this: @smallexample $ gcov -b tmp.c - 87.50% of 8 source lines executed in file tmp.c - 80.00% of 5 branches executed in file tmp.c - 80.00% of 5 branches taken at least once in file tmp.c - 50.00% of 2 calls executed in file tmp.c +90.00% of 10 source lines executed in file tmp.c +80.00% of 5 branches executed in file tmp.c +80.00% of 5 branches taken at least once in file tmp.c +50.00% of 2 calls executed in file tmp.c Creating tmp.c.gcov. @end smallexample Here is a sample of a resulting @file{tmp.c.gcov} file: @smallexample - main() - @{ - 1 int i, total; - - 1 total = 0; - - 11 for (i = 0; i < 10; i++) -branch 0 taken = 91% -branch 1 taken = 100% -branch 2 taken = 100% - 10 total += i; - - 1 if (total != 45) -branch 0 taken = 100% - ###### printf ("Failure\n"); -call 0 never executed -branch 1 never executed - else - 1 printf ("Success\n"); -call 0 returns = 100% - 1 @} + -: 0:Source:tmp.c + -: 0:Object:tmp.bb + -: 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++) +branch 0: taken 90% +branch 1: taken 100% +branch 2: taken 100% + 10: 10: total += i; + -: 11: + 1: 12: if (total != 45) +branch 0: taken 100% + #####: 13: printf ("Failure\n"); +call 0: never executed +branch 1: never executed + -: 14: else + 1: 15: printf ("Success\n"); +call 0: returns 100% + 1: 16: return 0; + 1: 17:@} @end smallexample For each basic block, a line is printed after the last line of the basic @@ -286,11 +340,11 @@ provide more accurate long-term information over a large number of program runs. The data in the @file{.da} files is saved immediately before the program -exits. For each source file compiled with @option{-fprofile-arcs}, the profiling -code first attempts to read in an existing @file{.da} file; if the file -doesn't match the executable (differing number of basic block counts) it -will ignore the contents of the file. It then adds in the new execution -counts and finally writes the data to the file. +exits. For each source file compiled with @option{-fprofile-arcs}, the +profiling code first attempts to read in an existing @file{.da} file; if +the file doesn't match the executable (differing number of basic block +counts) it will ignore the contents of the file. It then adds in the +new execution counts and finally writes the data to the file. @node Gcov and Optimization @section Using @command{gcov} with GCC Optimization @@ -319,10 +373,10 @@ the @command{gcov} output looks like this if you compiled the program with optimization: @smallexample - 100 if (a != b) - 100 c = 1; - 100 else - 100 c = 0; + 100: 12:if (a != b) + 100: 13: c = 1; + 100: 14:else + 100: 15: c = 0; @end smallexample The output shows that this block of code, combined by optimization, diff --git a/gcc/gcov.c b/gcc/gcov.c index 01e1a1dd77e..9925e95999b 100644 --- a/gcc/gcov.c +++ b/gcc/gcov.c @@ -160,6 +160,10 @@ struct bb_info_list { static struct bb_info_list *bb_graph_list = 0; +/* Modification time of data files. */ + +static time_t bb_file_time; + /* Name and file pointer of the input file for the basic block graph. */ static char *bbg_file_name; @@ -212,11 +216,15 @@ static int output_long_names = 0; static int output_function_summary = 0; -/* Object directory file prefix. This is the directory where .bb and .bbg - files are looked for, if non-zero. */ +/* Object directory file prefix. This is the directory/file + where .bb and .bbg files are looked for, if non-zero. */ static char *object_directory = 0; +/* Preserve all pathname components. Needed when object files and + source files are in subdirectories. */ +static int preserve_paths = 0; + /* Output the number of times a branch was taken as opposed to the percentage of times it was taken. Turned on by the -c option */ @@ -238,6 +246,7 @@ static void solve_program_flow_graph PARAMS ((struct bb_info_list *)); static void calculate_branch_probs PARAMS ((struct bb_info_list *, int, struct arcdata **, int)); static void function_summary PARAMS ((void)); +static const char *format_hwint PARAMS ((HOST_WIDEST_INT, HOST_WIDEST_INT, int)); extern int main PARAMS ((int, char **)); @@ -304,7 +313,8 @@ print_usage (error_p) fnotice (file, " -l, --long-file-names Use long output file names for included\n\ source files\n"); fnotice (file, " -f, --function-summaries Output summaries for each function\n"); - fnotice (file, " -o, --object-directory OBJDIR Search for object files in OBJDIR\n"); + fnotice (file, " -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n"); + fnotice (file, " -p, --preserve-paths Preserve all pathname components\n"); fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n", GCCBUGURL); exit (status); @@ -332,7 +342,9 @@ static const struct option options[] = { "no-output", no_argument, NULL, 'n' }, { "long-file-names", no_argument, NULL, 'l' }, { "function-summaries", no_argument, NULL, 'f' }, - { "object-directory", required_argument, NULL, 'o' } + { "preserve-paths", no_argument, NULL, 'p' }, + { "object-directory", required_argument, NULL, 'o' }, + { "object-file", required_argument, NULL, 'o' }, }; /* Parse the command line. */ @@ -344,7 +356,7 @@ process_args (argc, argv) { int opt; - while ((opt = getopt_long (argc, argv, "hvbclnfo:", options, NULL)) != -1) + while ((opt = getopt_long (argc, argv, "hvbclnfo:p", options, NULL)) != -1) { switch (opt) { @@ -372,6 +384,9 @@ process_args (argc, argv) case 'o': object_directory = optarg; break; + case 'p': + preserve_paths = 1; + break; default: print_usage (true); /* print_usage will exit. */ @@ -385,77 +400,66 @@ process_args (argc, argv) } -/* Find and open the .bb, .da, and .bbg files. */ +/* Find and open the .bb, .da, and .bbg files. If OBJECT_DIRECTORY is + not specified, these are looked for in the current directory, and + named from the basename of the input_file_name sans extension. If + OBJECT_DIRECTORY is specified and is a directory, the files are in + that directory, but named from the basename of the input_file_name, + sans extension. Otherwise OBJECT_DIRECTORY is taken to be the name + of the object *file*, and the data files are named from that. */ static void open_files () { - int count, objdir_count; char *cptr; - - /* Determine the names of the .bb, .bbg, and .da files. Strip off the - extension, if any, and append the new extensions. */ - count = strlen (input_file_name); - if (object_directory) - objdir_count = strlen (object_directory); - else - objdir_count = 0; - - da_file_name = xmalloc (count + objdir_count + 4); - bb_file_name = xmalloc (count + objdir_count + 4); - bbg_file_name = xmalloc (count + objdir_count + 5); - - if (object_directory) + char *name; + int length = strlen (input_file_name); + int base; + + if (object_directory && object_directory[0]) { - strcpy (da_file_name, object_directory); - strcpy (bb_file_name, object_directory); - strcpy (bbg_file_name, object_directory); - - if (object_directory[objdir_count - 1] != '/') - { - strcat (da_file_name, "/"); - strcat (bb_file_name, "/"); - strcat (bbg_file_name, "/"); - } - - cptr = strrchr (input_file_name, '/'); - if (cptr) - { - strcat (da_file_name, cptr + 1); - strcat (bb_file_name, cptr + 1); - strcat (bbg_file_name, cptr + 1); - } - else - { - strcat (da_file_name, input_file_name); - strcat (bb_file_name, input_file_name); - strcat (bbg_file_name, input_file_name); - } + struct stat status; + + length += strlen (object_directory) + 2; + name = xmalloc (length); + name[0] = 0; + + base = !stat (object_directory, &status) && S_ISDIR (status.st_mode); + strcat (name, object_directory); + if (base && name[strlen (name) - 1] != '/') + strcat (name, "/"); } else { - strcpy (da_file_name, input_file_name); - strcpy (bb_file_name, input_file_name); - strcpy (bbg_file_name, input_file_name); + name = xmalloc (length + 1); + name[0] = 0; + base = 1; } + + if (base) + { + /* Append source file name */ + cptr = strrchr (input_file_name, '/'); + cptr = cptr ? cptr + 1 : input_file_name; - cptr = strrchr (bb_file_name, '.'); - if (cptr) - strcpy (cptr, ".bb"); - else - strcat (bb_file_name, ".bb"); - - cptr = strrchr (da_file_name, '.'); - if (cptr) - strcpy (cptr, ".da"); - else - strcat (da_file_name, ".da"); - - cptr = strrchr (bbg_file_name, '.'); + strcat (name, cptr); + } + /* Remove the extension. */ + cptr = strrchr (name, '.'); if (cptr) - strcpy (cptr, ".bbg"); - else - strcat (bbg_file_name, ".bbg"); + *cptr = 0; + + length = strlen (name); + da_file_name = xmalloc (length + 4); + bb_file_name = xmalloc (length + 4); + bbg_file_name = xmalloc (length + 5); + + strcpy (da_file_name, name); + strcpy (bb_file_name, name); + strcpy (bbg_file_name, name); + strcpy (da_file_name + length, ".da"); + strcpy (bb_file_name + length, ".bb"); + strcpy (bbg_file_name + length, ".bbg"); bb_file = fopen (bb_file_name, "rb"); if (bb_file == NULL) @@ -464,6 +468,21 @@ open_files () exit (FATAL_EXIT_CODE); } + bbg_file = fopen (bbg_file_name, "rb"); + if (bbg_file == NULL) + { + fnotice (stderr, "Could not open program flow graph file %s.\n", + bbg_file_name); + exit (FATAL_EXIT_CODE); + } + + { + struct stat status; + + if (!fstat (fileno (bb_file), &status)) + bb_file_time = status.st_mtime; + } + /* If none of the functions in the file were executed, then there won't be a .da file. Just assume that all counts are zero in this case. */ da_file = fopen (da_file_name, "rb"); @@ -473,14 +492,6 @@ open_files () fnotice (stderr, "Assuming that all execution counts are zero.\n"); } - bbg_file = fopen (bbg_file_name, "rb"); - if (bbg_file == NULL) - { - fnotice (stderr, "Could not open program flow graph file %s.\n", - bbg_file_name); - exit (FATAL_EXIT_CODE); - } - /* Check for empty .bbg file. This indicates that there is no executable code in this source file. */ /* Set the EOF condition if at the end of file. */ @@ -554,7 +565,6 @@ read_profile (function_name, cfg_checksum, instr_arcs) int function_name_buffer_len; profile = xmalloc (sizeof (gcov_type) * instr_arcs); - rewind (da_file); function_name_buffer_len = strlen (function_name) + 1; function_name_buffer = xmalloc (function_name_buffer_len + 1); @@ -564,6 +574,7 @@ read_profile (function_name, cfg_checksum, instr_arcs) if (!da_file) return profile; + rewind (da_file); while (1) { long magic, extra_bytes; @@ -1100,15 +1111,64 @@ calculate_branch_probs (current_graph, block_num, branch_probs, last_line_num) } } +/* Format a HOST_WIDE_INT as either a percent ratio, or absolute + count. If dp >= 0, format TOP/BOTTOM * 100 to DP decimal places. + If DP is zero, no decimal point is printed. Only print 100% when + TOP==BOTTOM and only print 0% when TOP=0. If dp < 0, then simply + format TOP. Return pointer to a static string. */ + +static char const * +format_hwint (top, bottom, dp) + HOST_WIDEST_INT top, bottom; + int dp; +{ + static char buffer[20]; + + if (dp >= 0) + { + float ratio = bottom ? (float)top / bottom : 0; + int ix; + unsigned limit = 100; + unsigned percent; + + for (ix = dp; ix--; ) + limit *= 10; + + percent = (unsigned) (ratio * limit); + if (!percent && top) + percent = 1; + else if (percent == limit && top != bottom) + percent = limit - 1; + ix = sprintf (buffer, "%.*u%%", dp + 1, percent); + if (dp) + { + dp++; + do + { + buffer[ix+1] = buffer[ix]; + ix--; + } + while (dp--); + buffer[ix + 1] = '.'; + } + } + else + sprintf (buffer, HOST_WIDEST_INT_PRINT_DEC, top); + + return buffer; +} + + /* Output summary info for a function. */ static void function_summary () { if (function_source_lines) - fnotice (stdout, "%6.2f%% of %d source lines executed in function %s\n", - (((double) function_source_lines_executed / function_source_lines) - * 100), function_source_lines, function_name); + fnotice (stdout, "%s of %d source lines executed in function %s\n", + format_hwint (function_source_lines_executed, + function_source_lines, 2), + function_source_lines, function_name); else fnotice (stdout, "No executable source lines in function %s\n", function_name); @@ -1117,20 +1177,23 @@ function_summary () { if (function_branches) { - fnotice (stdout, "%6.2f%% of %d branches executed in function %s\n", - (((double) function_branches_executed / function_branches) - * 100), function_branches, function_name); + fnotice (stdout, "%s of %d branches executed in function %s\n", + format_hwint (function_branches_executed, + function_branches, 2), + function_branches, function_name); fnotice (stdout, - "%6.2f%% of %d branches taken at least once in function %s\n", - (((double) function_branches_taken / function_branches) - * 100), function_branches, function_name); + "%s of %d branches taken at least once in function %s\n", + format_hwint (function_branches_taken, + function_branches, 2), + function_branches, function_name); } else fnotice (stdout, "No branches in function %s\n", function_name); if (function_calls) - fnotice (stdout, "%6.2f%% of %d calls executed in function %s\n", - (((double) function_calls_executed / function_calls) - * 100), function_calls, function_name); + fnotice (stdout, "%s of %d calls executed in function %s\n", + format_hwint (function_calls_executed, + function_calls, 2), + function_calls, function_name); else fnotice (stdout, "No calls in function %s\n", function_name); } @@ -1185,21 +1248,7 @@ output_data () for (s_ptr = sources; s_ptr; s_ptr = s_ptr->next) { - /* If this is a relative file name, and an object directory has been - specified, then make it relative to the object directory name. */ - if (! IS_ABSOLUTE_PATHNAME (s_ptr->name) - && object_directory != 0 - && *object_directory != '\0') - { - int objdir_count = strlen (object_directory); - source_file_name = xmalloc (objdir_count + strlen (s_ptr->name) + 2); - strcpy (source_file_name, object_directory); - if (object_directory[objdir_count - 1] != '/') - source_file_name[objdir_count++] = '/'; - strcpy (source_file_name + objdir_count, s_ptr->name); - } - else - source_file_name = s_ptr->name; + source_file_name = s_ptr->name; line_counts = (gcov_type *) xcalloc (sizeof (gcov_type), s_ptr->maxlineno); line_exists = xcalloc (1, s_ptr->maxlineno); @@ -1376,9 +1425,10 @@ output_data () if (total_source_lines) fnotice (stdout, - "%6.2f%% of %d source lines executed in file %s\n", - (((double) total_source_lines_executed / total_source_lines) - * 100), total_source_lines, source_file_name); + "%s of %d source lines executed in file %s\n", + format_hwint (total_source_lines_executed, + total_source_lines, 2), + total_source_lines, source_file_name); else fnotice (stdout, "No executable source lines in file %s\n", source_file_name); @@ -1387,20 +1437,22 @@ output_data () { if (total_branches) { - fnotice (stdout, "%6.2f%% of %d branches executed in file %s\n", - (((double) total_branches_executed / total_branches) - * 100), total_branches, source_file_name); + fnotice (stdout, "%s of %d branches executed in file %s\n", + format_hwint (total_branches_executed, + total_branches, 2), + total_branches, source_file_name); fnotice (stdout, - "%6.2f%% of %d branches taken at least once in file %s\n", - (((double) total_branches_taken / total_branches) - * 100), total_branches, source_file_name); + "%s of %d branches taken at least once in file %s\n", + format_hwint (total_branches_taken, + total_branches, 2), + total_branches, source_file_name); } else fnotice (stdout, "No branches in file %s\n", source_file_name); if (total_calls) - fnotice (stdout, "%6.2f%% of %d calls executed in file %s\n", - (((double) total_calls_executed / total_calls) - * 100), total_calls, source_file_name); + fnotice (stdout, "%s of %d calls executed in file %s\n", + format_hwint (total_calls_executed, total_calls, 2), + total_calls, source_file_name); else fnotice (stdout, "No calls in file %s\n", source_file_name); } @@ -1410,51 +1462,70 @@ output_data () /* Now the statistics are ready. Read in the source file one line at a time, and output that line to the gcov file preceded by its execution count if non zero. */ - - source_file = fopen (source_file_name, "r"); - if (source_file == NULL) + char const *retval; + + /* Generate an output file name. LONG_OUTPUT_NAMES and + PRESERVE_PATHS affect name generation. With + preserve_paths we create a filename from all path + components of the source file, replacing '/' with '#', + without it we simply take the basename component. With + long_output_names we prepend the processed name of the + input file to each output name (except when the current + source file is the input file, so you don't get a double + concatenation). The two components are separated by + '##'. Also '.' filename components are removed and '..' + components are renamed to '^'. */ + gcov_file_name = xmalloc (strlen (source_file_name) + + strlen (input_file_name) + 10); + gcov_file_name[0] = 0; + if (output_long_names && strcmp (source_file_name, input_file_name)) { - fnotice (stderr, "Could not open source file %s.\n", - source_file_name); - free (line_counts); - free (line_exists); - continue; + /* Generate the input filename part. */ + cptr = preserve_paths ? NULL : strrchr (input_file_name, '/'); + cptr = cptr ? cptr + 1 : input_file_name; + strcat (gcov_file_name, cptr); + strcat (gcov_file_name, "##"); } + /* Generate the source filename part. */ + cptr = preserve_paths ? NULL : strrchr (source_file_name, '/'); + cptr = cptr ? cptr + 1 : source_file_name; + strcat (gcov_file_name, cptr); - count = strlen (source_file_name); - cptr = strrchr (s_ptr->name, '/'); - if (cptr) - cptr = cptr + 1; - else - cptr = s_ptr->name; - if (output_long_names && strcmp (cptr, input_file_name)) - { - gcov_file_name = xmalloc (count + 7 + strlen (input_file_name)); - - cptr = strrchr (input_file_name, '/'); - if (cptr) - strcpy (gcov_file_name, cptr + 1); - else - strcpy (gcov_file_name, input_file_name); - - strcat (gcov_file_name, "."); - - cptr = strrchr (source_file_name, '/'); - if (cptr) - strcat (gcov_file_name, cptr + 1); - else - strcat (gcov_file_name, source_file_name); - } - else + if (preserve_paths) { - gcov_file_name = xmalloc (count + 6); - cptr = strrchr (source_file_name, '/'); - if (cptr) - strcpy (gcov_file_name, cptr + 1); - else - strcpy (gcov_file_name, source_file_name); + /* Convert '/' to '#', remove '/./', convert '/../' to + '/^/' */ + char *prev; + + for (cptr = gcov_file_name; + (cptr = strchr ((prev = cptr), '/'));) + { + unsigned shift = 0; + + if (prev + 1 == cptr && prev[0] == '.') + { + /* Remove '.' */ + shift = 2; + } + else if (prev + 2 == cptr + && prev[0] == '.' && prev[1] == '.') + { + /* Convert '..' */ + shift = 1; + prev[1] = '^'; + } + else + *cptr++ = '#'; + if (shift) + { + cptr = prev; + do + prev[0] = prev[shift]; + while (*prev++); + } + } } - + /* Don't strip off the ending for compatibility with tcov, since this results in confusion if there is more than one file with the same basename, e.g. tmp.c and tmp.h. */ @@ -1466,7 +1537,6 @@ output_data () { fnotice (stderr, "Could not open output file %s.\n", gcov_file_name); - fclose (source_file); free (line_counts); free (line_exists); continue; @@ -1474,44 +1544,61 @@ output_data () fnotice (stdout, "Creating %s.\n", gcov_file_name); - for (count = 1; count < s_ptr->maxlineno; count++) + fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, source_file_name); + fprintf (gcov_file, "%9s:%5d:Object:%s\n", "-", 0, bb_file_name); + + source_file = fopen (source_file_name, "r"); + if (source_file == NULL) + fnotice (stderr, "Could not open source file %s.\n", + source_file_name); + else { - char *retval; - int len; - - retval = fgets (string, STRING_SIZE, source_file); + struct stat status; - /* For lines which don't exist in the .bb file, print nothing - before the source line. For lines which exist but were never - executed, print ###### before the source line. Otherwise, - print the execution count before the source line. */ + if (!fstat (fileno (source_file), &status) + && status.st_mtime > bb_file_time) + { + fnotice (stderr, "Warning: source file %s is newer than %s\n", + source_file_name, bb_file_name); + fprintf (gcov_file, "%9s:%5d:Source is newer than compiler output\n", "-", 0); + } + } + + for (retval = source_file ? "" : NULL, count = 1; + count < s_ptr->maxlineno; count++) + { + /* For lines which don't exist in the .bb file, print + '-' before the source line. For lines which exist + but were never executed, print '#####' 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. */ - if (line_exists[count]) + fprintf (gcov_file, "%9s:%5ld:", + !line_exists[count] ? "-" + : !line_counts[count] ? "#####" + : format_hwint (line_counts[count], 0, -1), count); + + if (retval) { - if (line_counts[count]) + do { - char c[20]; - sprintf (c, HOST_WIDEST_INT_PRINT_DEC, (HOST_WIDEST_INT)line_counts[count]); - fprintf (gcov_file, "%12s %s", c, - string); + retval = fgets (string, STRING_SIZE, source_file); + if (!retval) + { + fnotice (stderr, + "Unexpected EOF while reading source file %s.\n", + source_file_name); + break; + } + fputs (retval, gcov_file); } - else - fprintf (gcov_file, " ###### %s", string); - } - else - fprintf (gcov_file, "\t\t%s", string); - - /* In case the source file line is larger than our buffer, keep - reading and outputting lines until we get a newline. */ - len = strlen (string); - while ((len == 0 || string[strlen (string) - 1] != '\n') - && retval != NULL) - { - retval = fgets (string, STRING_SIZE, source_file); - fputs (string, gcov_file); + while (!retval[0] || retval[strlen (retval) - 1] != '\n'); } - + if (!retval) + fputs ("??\n", gcov_file); + if (output_branch_probs) { for (i = 0, a_ptr = branch_probs[count]; a_ptr; @@ -1520,95 +1607,53 @@ output_data () if (a_ptr->call_insn) { if (a_ptr->total == 0) - fnotice (gcov_file, "call %d never executed\n", i); - else - { - if (output_branch_counts) - { - char c[20]; - sprintf (c, HOST_WIDEST_INT_PRINT_DEC, - a_ptr->total - a_ptr->hits); - fnotice (gcov_file, - "call %d returns = %s\n", i, c); - } - else - { - char c[20]; - sprintf (c, HOST_WIDEST_INT_PRINT_DEC, - 100 - ((a_ptr->hits * 100) - + (a_ptr->total >> 1)) - / a_ptr->total); - fnotice (gcov_file, - "call %d returns = %s%%\n", i, c); - } - } + fnotice (gcov_file, "call %2d: never executed\n", i); + else + fnotice + (gcov_file, "call %2d: returns %s\n", i, + format_hwint (a_ptr->total - a_ptr->hits, + a_ptr->total, + -output_branch_counts)); } else { if (a_ptr->total == 0) - fnotice (gcov_file, "branch %d never executed\n", + fnotice (gcov_file, "branch %2d: never executed\n", i); else - { - if (output_branch_counts) - { - char c[20]; - sprintf (c, HOST_WIDEST_INT_PRINT_DEC, - a_ptr->hits); - fnotice (gcov_file, - "branch %d taken = %s\n", i, c); - } - else - { - char c[20]; - sprintf (c, HOST_WIDEST_INT_PRINT_DEC, - ((a_ptr->hits * 100) - + (a_ptr->total >> 1)) - / a_ptr->total); - fnotice (gcov_file, - "branch %d taken = %s%%\n", i, c); - } - } + fnotice + (gcov_file, "branch %2d: taken %s\n", i, + format_hwint (a_ptr->hits, a_ptr->total, + -output_branch_counts)); } } } - - /* Gracefully handle errors while reading the source file. */ - if (retval == NULL) - { - fnotice (stderr, - "Unexpected EOF while reading source file %s.\n", - source_file_name); - break; - } } /* Handle all remaining source lines. There may be lines after the last line of code. */ + if (retval) + { + for (; (retval = fgets (string, STRING_SIZE, source_file)); + count++) + { + fprintf (gcov_file, "%9s:%5ld:%s", "-", count, retval); - { - char *retval = fgets (string, STRING_SIZE, source_file); - while (retval != NULL) - { - int len; - - fprintf (gcov_file, "\t\t%s", string); - - /* In case the source file line is larger than our buffer, keep - reading and outputting lines until we get a newline. */ - len = strlen (string); - while ((len == 0 || string[strlen (string) - 1] != '\n') - && retval != NULL) - { - retval = fgets (string, STRING_SIZE, source_file); - fputs (string, gcov_file); - } - - retval = fgets (string, STRING_SIZE, source_file); - } - } + while (!retval[0] || retval[strlen (retval) - 1] != '\n') + { + retval = fgets (string, STRING_SIZE, source_file); + if (!retval) + break; + fputs (retval, gcov_file); + } + } + } - fclose (source_file); + if (source_file) + fclose (source_file); + if (ferror (gcov_file)) + fnotice (stderr, "Error writing output file %s.\n", + gcov_file_name); fclose (gcov_file); } -- 2.11.0