| /* Gcov.c: prepend line execution counts and branch probabilities to a |
| source file. |
| Copyright (C) 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, |
| 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. |
| Contributed by James E. Wilson of Cygnus Support. |
| Mangled by Bob Manson of Cygnus Support. |
| Mangled further by Nathan Sidwell <nathan@codesourcery.com> |
| |
| Gcov is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| Gcov is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with Gcov; see the file COPYING. If not, write to |
| the Free Software Foundation, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. */ |
| |
| /* ??? Print a list of the ten blocks with the highest execution counts, |
| and list the line numbers corresponding to those blocks. Also, perhaps |
| list the line numbers with the highest execution counts, only printing |
| the first if there are several which are all listed in the same block. */ |
| |
| /* ??? Should have an option to print the number of basic blocks, and the |
| percent of them that are covered. */ |
| |
| /* ??? Does not correctly handle the case where two .bb files refer to |
| the same included source file. For example, if one has a short |
| file containing only inline functions, which is then included in |
| two other files, then there will be two .bb files which refer to |
| the include file, but there is no way to get the total execution |
| counts for the included file, can only get execution counts for one |
| or the other of the including files. this can be fixed by --ratios |
| --long-file-names --preserve-paths and perl. */ |
| |
| /* Need an option to show individual block counts, and show |
| probabilities of fall through arcs. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "intl.h" |
| #include "version.h" |
| |
| #include <getopt.h> |
| |
| #define IN_GCOV 1 |
| #include "gcov-io.h" |
| #include "gcov-io.c" |
| |
| /* The bbg file is generated by -ftest-coverage option. The da file is |
| generated by a program compiled with -fprofile-arcs. Their formats |
| are documented in gcov-io.h. */ |
| |
| /* The functions in this file for creating and solution program flow graphs |
| are very similar to functions in the gcc source file profile.c. In |
| some places we make use of the knowledge of how profile.c works to |
| select particular algorithms here. */ |
| |
| /* This is the size of the buffer used to read in source file lines. */ |
| |
| #define STRING_SIZE 200 |
| |
| struct function_info; |
| struct block_info; |
| struct source_info; |
| |
| /* Describes an arc between two basic blocks. */ |
| |
| typedef struct arc_info |
| { |
| /* source and destination blocks. */ |
| struct block_info *src; |
| struct block_info *dst; |
| |
| /* transition counts. */ |
| gcov_type count; |
| /* used in cycle search, so that we do not clobber original counts. */ |
| gcov_type cs_count; |
| |
| unsigned int count_valid : 1; |
| unsigned int on_tree : 1; |
| unsigned int fake : 1; |
| unsigned int fall_through : 1; |
| |
| /* Arc is for a function that abnormally returns. */ |
| unsigned int is_call_non_return : 1; |
| |
| /* Arc is for catch/setjump. */ |
| unsigned int is_nonlocal_return : 1; |
| |
| /* Is an unconditional branch. */ |
| unsigned int is_unconditional : 1; |
| |
| /* Loop making arc. */ |
| unsigned int cycle : 1; |
| |
| /* Next branch on line. */ |
| struct arc_info *line_next; |
| |
| /* Links to next arc on src and dst lists. */ |
| struct arc_info *succ_next; |
| struct arc_info *pred_next; |
| } arc_t; |
| |
| /* Describes a basic block. Contains lists of arcs to successor and |
| predecessor blocks. */ |
| |
| typedef struct block_info |
| { |
| /* Chain of exit and entry arcs. */ |
| arc_t *succ; |
| arc_t *pred; |
| |
| /* Number of unprocessed exit and entry arcs. */ |
| gcov_type num_succ; |
| gcov_type num_pred; |
| |
| /* Block execution count. */ |
| gcov_type count; |
| unsigned flags : 13; |
| unsigned count_valid : 1; |
| unsigned valid_chain : 1; |
| unsigned invalid_chain : 1; |
| |
| /* Block is a call instrumenting site. */ |
| unsigned is_call_site : 1; /* Does the call. */ |
| unsigned is_call_return : 1; /* Is the return. */ |
| |
| /* Block is a landing pad for longjmp or throw. */ |
| unsigned is_nonlocal_return : 1; |
| |
| union |
| { |
| struct |
| { |
| /* Array of line numbers and source files. source files are |
| introduced by a linenumber of zero, the next 'line number' is |
| the number of the source file. Always starts with a source |
| file. */ |
| unsigned *encoding; |
| unsigned num; |
| } line; /* Valid until blocks are linked onto lines */ |
| struct |
| { |
| /* Single line graph cycle workspace. Used for all-blocks |
| mode. */ |
| arc_t *arc; |
| unsigned ident; |
| } cycle; /* Used in all-blocks mode, after blocks are linked onto |
| lines. */ |
| } u; |
| |
| /* Temporary chain for solving graph, and for chaining blocks on one |
| line. */ |
| struct block_info *chain; |
| |
| } block_t; |
| |
| /* Describes a single function. Contains an array of basic blocks. */ |
| |
| typedef struct function_info |
| { |
| /* Name of function. */ |
| char *name; |
| unsigned ident; |
| unsigned checksum; |
| |
| /* Array of basic blocks. */ |
| block_t *blocks; |
| unsigned num_blocks; |
| unsigned blocks_executed; |
| |
| /* Raw arc coverage counts. */ |
| gcov_type *counts; |
| unsigned num_counts; |
| |
| /* First line number. */ |
| unsigned line; |
| struct source_info *src; |
| |
| /* Next function in same source file. */ |
| struct function_info *line_next; |
| |
| /* Next function. */ |
| struct function_info *next; |
| } function_t; |
| |
| /* Describes coverage of a file or function. */ |
| |
| typedef struct coverage_info |
| { |
| int lines; |
| int lines_executed; |
| |
| int branches; |
| int branches_executed; |
| int branches_taken; |
| |
| int calls; |
| int calls_executed; |
| |
| char *name; |
| } coverage_t; |
| |
| /* Describes a single line of source. Contains a chain of basic blocks |
| with code on it. */ |
| |
| typedef struct line_info |
| { |
| gcov_type count; /* execution count */ |
| union |
| { |
| arc_t *branches; /* branches from blocks that end on this |
| line. Used for branch-counts when not |
| all-blocks mode. */ |
| block_t *blocks; /* blocks which start on this line. Used |
| in all-blocks mode. */ |
| } u; |
| unsigned exists : 1; |
| } line_t; |
| |
| /* Describes a file mentioned in the block graph. Contains an array |
| of line info. */ |
| |
| typedef struct source_info |
| { |
| /* Name of source file. */ |
| char *name; |
| unsigned index; |
| |
| /* Array of line information. */ |
| line_t *lines; |
| unsigned num_lines; |
| |
| coverage_t coverage; |
| |
| /* Functions in this source file. These are in ascending line |
| number order. */ |
| function_t *functions; |
| |
| /* Next source file. */ |
| struct source_info *next; |
| } source_t; |
| |
| /* Holds a list of function basic block graphs. */ |
| |
| static function_t *functions; |
| |
| /* This points to the head of the sourcefile structure list. */ |
| |
| static source_t *sources; |
| |
| /* This holds data summary information. */ |
| |
| static struct gcov_summary object_summary; |
| static unsigned program_count; |
| |
| /* Modification time of graph file. */ |
| |
| static time_t bbg_file_time; |
| |
| /* Name and file pointer of the input file for the basic block graph. */ |
| |
| static char *bbg_file_name; |
| |
| /* Stamp of the bbg file */ |
| static unsigned bbg_stamp; |
| |
| /* Name and file pointer of the input file for the arc count data. */ |
| |
| static char *da_file_name; |
| |
| /* Output branch probabilities. */ |
| |
| static int flag_branches = 0; |
| |
| /* Show unconditional branches too. */ |
| static int flag_unconditional = 0; |
| |
| /* Output a gcov file if this is true. This is on by default, and can |
| be turned off by the -n option. */ |
| |
| static int flag_gcov_file = 1; |
| |
| /* For included files, make the gcov output file name include the name |
| of the input source file. For example, if x.h is included in a.c, |
| then the output file name is a.c##x.h.gcov instead of x.h.gcov. */ |
| |
| static int flag_long_names = 0; |
| |
| /* Output count information for every basic block, not merely those |
| that contain line number information. */ |
| |
| static int flag_all_blocks = 0; |
| |
| /* Output summary info for each function. */ |
| |
| static int flag_function_summary = 0; |
| |
| /* Object directory file prefix. This is the directory/file where the |
| graph and data files are looked for, if nonzero. */ |
| |
| static char *object_directory = 0; |
| |
| /* Preserve all pathname components. Needed when object files and |
| source files are in subdirectories. '/' is mangled as '#', '.' is |
| elided and '..' mangled to '^'. */ |
| |
| static int flag_preserve_paths = 0; |
| |
| /* Output the number of times a branch was taken as opposed to the percentage |
| of times it was taken. */ |
| |
| static int flag_counts = 0; |
| |
| /* Forward declarations. */ |
| static void fnotice (FILE *, const char *, ...) ATTRIBUTE_PRINTF_2; |
| static int process_args (int, char **); |
| static void print_usage (int) ATTRIBUTE_NORETURN; |
| static void print_version (void) ATTRIBUTE_NORETURN; |
| static void process_file (const char *); |
| static void create_file_names (const char *); |
| static source_t *find_source (const char *); |
| static int read_graph_file (void); |
| static int read_count_file (void); |
| static void solve_flow_graph (function_t *); |
| static void add_branch_counts (coverage_t *, const arc_t *); |
| static void add_line_counts (coverage_t *, function_t *); |
| static void function_summary (const coverage_t *, const char *); |
| static const char *format_gcov (gcov_type, gcov_type, int); |
| static void accumulate_line_counts (source_t *); |
| static int output_branch_count (FILE *, int, const arc_t *); |
| static void output_lines (FILE *, const source_t *); |
| static char *make_gcov_file_name (const char *, const char *); |
| static void release_structures (void); |
| extern int main (int, char **); |
| |
| int |
| main (int argc, char **argv) |
| { |
| int argno; |
| |
| /* Unlock the stdio streams. */ |
| unlock_std_streams (); |
| |
| gcc_init_libintl (); |
| |
| argno = process_args (argc, argv); |
| if (optind == argc) |
| print_usage (true); |
| |
| for (; argno != argc; argno++) |
| { |
| release_structures (); |
| |
| process_file (argv[argno]); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| fnotice (FILE *file, const char *cmsgid, ...) |
| { |
| va_list ap; |
| |
| va_start (ap, cmsgid); |
| vfprintf (file, _(cmsgid), ap); |
| va_end (ap); |
| } |
| |
| /* Print a usage message and exit. If ERROR_P is nonzero, this is an error, |
| otherwise the output of --help. */ |
| |
| static void |
| print_usage (int error_p) |
| { |
| FILE *file = error_p ? stderr : stdout; |
| int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE; |
| |
| fnotice (file, "Usage: gcov [OPTION]... SOURCEFILE\n\n"); |
| fnotice (file, "Print code coverage information.\n\n"); |
| fnotice (file, " -h, --help Print this help, then exit\n"); |
| fnotice (file, " -v, --version Print version number, then exit\n"); |
| fnotice (file, " -a, --all-blocks Show information for every basic block\n"); |
| fnotice (file, " -b, --branch-probabilities Include branch probabilities in output\n"); |
| fnotice (file, " -c, --branch-counts Given counts of branches taken\n\ |
| rather than percentages\n"); |
| fnotice (file, " -n, --no-output Do not create an output file\n"); |
| 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 DIR|FILE Search for object files in DIR or called FILE\n"); |
| fnotice (file, " -p, --preserve-paths Preserve all pathname components\n"); |
| fnotice (file, " -u, --unconditional-branches Show unconditional branch counts too\n"); |
| fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n", |
| bug_report_url); |
| exit (status); |
| } |
| |
| /* Print version information and exit. */ |
| |
| static void |
| print_version (void) |
| { |
| fnotice (stdout, "gcov (GCC) %s\n", version_string); |
| fprintf (stdout, "Copyright %s 2004 Free Software Foundation, Inc.\n", |
| _("(C)")); |
| fnotice (stdout, |
| _("This is free software; see the source for copying conditions.\n" |
| "There is NO warranty; not even for MERCHANTABILITY or \n" |
| "FITNESS FOR A PARTICULAR PURPOSE.\n\n")); |
| exit (SUCCESS_EXIT_CODE); |
| } |
| |
| static const struct option options[] = |
| { |
| { "help", no_argument, NULL, 'h' }, |
| { "version", no_argument, NULL, 'v' }, |
| { "all-blocks", no_argument, NULL, 'a' }, |
| { "branch-probabilities", no_argument, NULL, 'b' }, |
| { "branch-counts", no_argument, NULL, 'c' }, |
| { "no-output", no_argument, NULL, 'n' }, |
| { "long-file-names", no_argument, NULL, 'l' }, |
| { "function-summaries", no_argument, NULL, 'f' }, |
| { "preserve-paths", no_argument, NULL, 'p' }, |
| { "object-directory", required_argument, NULL, 'o' }, |
| { "object-file", required_argument, NULL, 'o' }, |
| { "unconditional-branches", no_argument, NULL, 'u' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| /* Process args, return index to first non-arg. */ |
| |
| static int |
| process_args (int argc, char **argv) |
| { |
| int opt; |
| |
| while ((opt = getopt_long (argc, argv, "abcfhlno:puv", options, NULL)) != -1) |
| { |
| switch (opt) |
| { |
| case 'a': |
| flag_all_blocks = 1; |
| break; |
| case 'b': |
| flag_branches = 1; |
| break; |
| case 'c': |
| flag_counts = 1; |
| break; |
| case 'f': |
| flag_function_summary = 1; |
| break; |
| case 'h': |
| print_usage (false); |
| /* print_usage will exit. */ |
| case 'l': |
| flag_long_names = 1; |
| break; |
| case 'n': |
| flag_gcov_file = 0; |
| break; |
| case 'o': |
| object_directory = optarg; |
| break; |
| case 'p': |
| flag_preserve_paths = 1; |
| break; |
| case 'u': |
| flag_unconditional = 1; |
| break; |
| case 'v': |
| print_version (); |
| /* print_version will exit. */ |
| default: |
| print_usage (true); |
| /* print_usage will exit. */ |
| } |
| } |
| |
| return optind; |
| } |
| |
| /* Process a single source file. */ |
| |
| static void |
| process_file (const char *file_name) |
| { |
| source_t *src; |
| function_t *fn; |
| |
| create_file_names (file_name); |
| if (read_graph_file ()) |
| return; |
| |
| if (!functions) |
| { |
| fnotice (stderr, "%s:no functions found\n", bbg_file_name); |
| return; |
| } |
| |
| if (read_count_file ()) |
| return; |
| |
| for (fn = functions; fn; fn = fn->next) |
| solve_flow_graph (fn); |
| for (src = sources; src; src = src->next) |
| src->lines = xcalloc (src->num_lines, sizeof (line_t)); |
| for (fn = functions; fn; fn = fn->next) |
| { |
| coverage_t coverage; |
| |
| memset (&coverage, 0, sizeof (coverage)); |
| coverage.name = fn->name; |
| add_line_counts (flag_function_summary ? &coverage : NULL, fn); |
| if (flag_function_summary) |
| { |
| function_summary (&coverage, "Function"); |
| fnotice (stdout, "\n"); |
| } |
| } |
| |
| for (src = sources; src; src = src->next) |
| { |
| accumulate_line_counts (src); |
| function_summary (&src->coverage, "File"); |
| if (flag_gcov_file) |
| { |
| char *gcov_file_name = make_gcov_file_name (file_name, src->name); |
| FILE *gcov_file = fopen (gcov_file_name, "w"); |
| |
| if (gcov_file) |
| { |
| fnotice (stdout, "%s:creating '%s'\n", |
| src->name, gcov_file_name); |
| output_lines (gcov_file, src); |
| if (ferror (gcov_file)) |
| fnotice (stderr, "%s:error writing output file '%s'\n", |
| src->name, gcov_file_name); |
| fclose (gcov_file); |
| } |
| else |
| fnotice (stderr, "%s:could not open output file '%s'\n", |
| src->name, gcov_file_name); |
| free (gcov_file_name); |
| } |
| fnotice (stdout, "\n"); |
| } |
| } |
| |
| /* Release all memory used. */ |
| |
| static void |
| release_structures (void) |
| { |
| function_t *fn; |
| source_t *src; |
| |
| free (bbg_file_name); |
| free (da_file_name); |
| da_file_name = bbg_file_name = NULL; |
| bbg_file_time = 0; |
| bbg_stamp = 0; |
| |
| while ((src = sources)) |
| { |
| sources = src->next; |
| |
| free (src->name); |
| free (src->lines); |
| } |
| |
| while ((fn = functions)) |
| { |
| unsigned ix; |
| block_t *block; |
| |
| functions = fn->next; |
| for (ix = fn->num_blocks, block = fn->blocks; ix--; block++) |
| { |
| arc_t *arc, *arc_n; |
| |
| for (arc = block->succ; arc; arc = arc_n) |
| { |
| arc_n = arc->succ_next; |
| free (arc); |
| } |
| } |
| free (fn->blocks); |
| free (fn->counts); |
| } |
| } |
| |
| /* Generate the names of the graph and data files. If OBJECT_DIRECTORY |
| is not specified, these are looked for in the current directory, |
| and named from the basename of the 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 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 |
| create_file_names (const char *file_name) |
| { |
| char *cptr; |
| char *name; |
| int length = strlen (file_name); |
| int base; |
| |
| if (object_directory && object_directory[0]) |
| { |
| 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 |
| { |
| name = xmalloc (length + 1); |
| name[0] = 0; |
| base = 1; |
| } |
| |
| if (base) |
| { |
| /* Append source file name. */ |
| cptr = strrchr (file_name, '/'); |
| strcat (name, cptr ? cptr + 1 : file_name); |
| } |
| |
| /* Remove the extension. */ |
| cptr = strrchr (name, '.'); |
| if (cptr) |
| *cptr = 0; |
| |
| length = strlen (name); |
| |
| bbg_file_name = xmalloc (length + strlen (GCOV_NOTE_SUFFIX) + 1); |
| strcpy (bbg_file_name, name); |
| strcpy (bbg_file_name + length, GCOV_NOTE_SUFFIX); |
| |
| da_file_name = xmalloc (length + strlen (GCOV_DATA_SUFFIX) + 1); |
| strcpy (da_file_name, name); |
| strcpy (da_file_name + length, GCOV_DATA_SUFFIX); |
| |
| return; |
| } |
| |
| /* Find or create a source file structure for FILE_NAME. Copies |
| FILE_NAME on creation */ |
| |
| static source_t * |
| find_source (const char *file_name) |
| { |
| source_t *src; |
| |
| if (!file_name) |
| file_name = "<unknown>"; |
| |
| for (src = sources; src; src = src->next) |
| if (!strcmp (file_name, src->name)) |
| return src; |
| |
| src = xcalloc (1, sizeof (source_t)); |
| src->name = xstrdup (file_name); |
| src->coverage.name = src->name; |
| src->index = sources ? sources->index + 1 : 1; |
| src->next = sources; |
| sources = src; |
| |
| return src; |
| } |
| |
| /* Read the graph file. Return nonzero on fatal error. */ |
| |
| static int |
| read_graph_file (void) |
| { |
| unsigned version; |
| unsigned current_tag = 0; |
| struct function_info *fn = NULL; |
| source_t *src = NULL; |
| unsigned ix; |
| unsigned tag; |
| |
| if (!gcov_open (bbg_file_name, 1)) |
| { |
| fnotice (stderr, "%s:cannot open graph file\n", bbg_file_name); |
| return 1; |
| } |
| bbg_file_time = gcov_time (); |
| if (!gcov_magic (gcov_read_unsigned (), GCOV_NOTE_MAGIC)) |
| { |
| fnotice (stderr, "%s:not a gcov graph file\n", bbg_file_name); |
| gcov_close (); |
| return 1; |
| } |
| |
| version = gcov_read_unsigned (); |
| if (version != GCOV_VERSION) |
| { |
| char v[4], e[4]; |
| |
| GCOV_UNSIGNED2STRING (v, version); |
| GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
| |
| fnotice (stderr, "%s:version '%.4s', prefer '%.4s'\n", |
| bbg_file_name, v, e); |
| } |
| bbg_stamp = gcov_read_unsigned (); |
| |
| while ((tag = gcov_read_unsigned ())) |
| { |
| unsigned length = gcov_read_unsigned (); |
| gcov_position_t base = gcov_position (); |
| |
| if (tag == GCOV_TAG_FUNCTION) |
| { |
| char *function_name; |
| unsigned ident, checksum, lineno; |
| source_t *src; |
| function_t *probe, *prev; |
| |
| ident = gcov_read_unsigned (); |
| checksum = gcov_read_unsigned (); |
| function_name = xstrdup (gcov_read_string ()); |
| src = find_source (gcov_read_string ()); |
| lineno = gcov_read_unsigned (); |
| |
| fn = xcalloc (1, sizeof (function_t)); |
| fn->name = function_name; |
| fn->ident = ident; |
| fn->checksum = checksum; |
| fn->src = src; |
| fn->line = lineno; |
| |
| fn->next = functions; |
| functions = fn; |
| current_tag = tag; |
| |
| if (lineno >= src->num_lines) |
| src->num_lines = lineno + 1; |
| /* 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. */ |
| for (probe = src->functions, prev = NULL; |
| probe && probe->line > lineno; |
| prev = probe, probe = probe->line_next) |
| continue; |
| fn->line_next = probe; |
| if (prev) |
| prev->line_next = fn; |
| else |
| src->functions = fn; |
| } |
| else if (fn && tag == GCOV_TAG_BLOCKS) |
| { |
| if (fn->blocks) |
| fnotice (stderr, "%s:already seen blocks for '%s'\n", |
| bbg_file_name, fn->name); |
| else |
| { |
| unsigned ix, num_blocks = GCOV_TAG_BLOCKS_NUM (length); |
| fn->num_blocks = num_blocks; |
| |
| fn->blocks = xcalloc (fn->num_blocks, sizeof (block_t)); |
| for (ix = 0; ix != num_blocks; ix++) |
| fn->blocks[ix].flags = gcov_read_unsigned (); |
| } |
| } |
| else if (fn && tag == GCOV_TAG_ARCS) |
| { |
| unsigned src = gcov_read_unsigned (); |
| unsigned num_dests = GCOV_TAG_ARCS_NUM (length); |
| |
| if (src >= fn->num_blocks || fn->blocks[src].succ) |
| goto corrupt; |
| |
| while (num_dests--) |
| { |
| struct arc_info *arc; |
| unsigned dest = gcov_read_unsigned (); |
| unsigned flags = gcov_read_unsigned (); |
| |
| if (dest >= fn->num_blocks) |
| goto corrupt; |
| arc = xcalloc (1, sizeof (arc_t)); |
| |
| arc->dst = &fn->blocks[dest]; |
| arc->src = &fn->blocks[src]; |
| |
| arc->count = 0; |
| arc->count_valid = 0; |
| arc->on_tree = !!(flags & GCOV_ARC_ON_TREE); |
| arc->fake = !!(flags & GCOV_ARC_FAKE); |
| arc->fall_through = !!(flags & GCOV_ARC_FALLTHROUGH); |
| |
| arc->succ_next = fn->blocks[src].succ; |
| fn->blocks[src].succ = arc; |
| fn->blocks[src].num_succ++; |
| |
| arc->pred_next = fn->blocks[dest].pred; |
| fn->blocks[dest].pred = arc; |
| fn->blocks[dest].num_pred++; |
| |
| if (arc->fake) |
| { |
| if (src) |
| { |
| /* Exceptional exit from this function, the |
| source block must be a call. */ |
| fn->blocks[src].is_call_site = 1; |
| arc->is_call_non_return = 1; |
| } |
| else |
| { |
| /* Non-local return from a callee of this |
| function. The destination block is a catch or |
| setjmp. */ |
| arc->is_nonlocal_return = 1; |
| fn->blocks[dest].is_nonlocal_return = 1; |
| } |
| } |
| |
| if (!arc->on_tree) |
| fn->num_counts++; |
| } |
| } |
| else if (fn && tag == GCOV_TAG_LINES) |
| { |
| unsigned blockno = gcov_read_unsigned (); |
| unsigned *line_nos = xcalloc (length - 1, sizeof (unsigned)); |
| |
| if (blockno >= fn->num_blocks || fn->blocks[blockno].u.line.encoding) |
| goto corrupt; |
| |
| for (ix = 0; ; ) |
| { |
| unsigned lineno = gcov_read_unsigned (); |
| |
| if (lineno) |
| { |
| if (!ix) |
| { |
| line_nos[ix++] = 0; |
| line_nos[ix++] = src->index; |
| } |
| line_nos[ix++] = lineno; |
| if (lineno >= src->num_lines) |
| src->num_lines = lineno + 1; |
| } |
| else |
| { |
| const char *file_name = gcov_read_string (); |
| |
| if (!file_name) |
| break; |
| src = find_source (file_name); |
| |
| line_nos[ix++] = 0; |
| line_nos[ix++] = src->index; |
| } |
| } |
| |
| fn->blocks[blockno].u.line.encoding = line_nos; |
| fn->blocks[blockno].u.line.num = ix; |
| } |
| else if (current_tag && !GCOV_TAG_IS_SUBTAG (current_tag, tag)) |
| { |
| fn = NULL; |
| current_tag = 0; |
| } |
| gcov_sync (base, length); |
| if (gcov_is_error ()) |
| { |
| corrupt:; |
| fnotice (stderr, "%s:corrupted\n", bbg_file_name); |
| gcov_close (); |
| return 1; |
| } |
| } |
| gcov_close (); |
| |
| /* We built everything backwards, so nreverse them all. */ |
| |
| /* Reverse sources. Not strictly necessary, but we'll then process |
| them in the 'expected' order. */ |
| { |
| source_t *src, *src_p, *src_n; |
| |
| for (src_p = NULL, src = sources; src; src_p = src, src = src_n) |
| { |
| src_n = src->next; |
| src->next = src_p; |
| } |
| sources = src_p; |
| } |
| |
| /* Reverse functions. */ |
| { |
| function_t *fn, *fn_p, *fn_n; |
| |
| for (fn_p = NULL, fn = functions; fn; fn_p = fn, fn = fn_n) |
| { |
| unsigned ix; |
| |
| fn_n = fn->next; |
| fn->next = fn_p; |
| |
| /* Reverse the arcs. */ |
| for (ix = fn->num_blocks; ix--;) |
| { |
| arc_t *arc, *arc_p, *arc_n; |
| |
| for (arc_p = NULL, arc = fn->blocks[ix].succ; arc; |
| arc_p = arc, arc = arc_n) |
| { |
| arc_n = arc->succ_next; |
| arc->succ_next = arc_p; |
| } |
| fn->blocks[ix].succ = arc_p; |
| |
| for (arc_p = NULL, arc = fn->blocks[ix].pred; arc; |
| arc_p = arc, arc = arc_n) |
| { |
| arc_n = arc->pred_next; |
| arc->pred_next = arc_p; |
| } |
| fn->blocks[ix].pred = arc_p; |
| } |
| } |
| functions = fn_p; |
| } |
| return 0; |
| } |
| |
| /* Reads profiles from the count file and attach to each |
| function. Return nonzero if fatal error. */ |
| |
| static int |
| read_count_file (void) |
| { |
| unsigned ix; |
| unsigned version; |
| unsigned tag; |
| function_t *fn = NULL; |
| int error = 0; |
| |
| if (!gcov_open (da_file_name, 1)) |
| { |
| fnotice (stderr, "%s:cannot open data file\n", da_file_name); |
| return 1; |
| } |
| if (!gcov_magic (gcov_read_unsigned (), GCOV_DATA_MAGIC)) |
| { |
| fnotice (stderr, "%s:not a gcov data file\n", da_file_name); |
| cleanup:; |
| gcov_close (); |
| return 1; |
| } |
| version = gcov_read_unsigned (); |
| if (version != GCOV_VERSION) |
| { |
| char v[4], e[4]; |
| |
| GCOV_UNSIGNED2STRING (v, version); |
| GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
| |
| fnotice (stderr, "%s:version '%.4s', prefer version '%.4s'\n", |
| da_file_name, v, e); |
| } |
| tag = gcov_read_unsigned (); |
| if (tag != bbg_stamp) |
| { |
| fnotice (stderr, "%s:stamp mismatch with graph file\n", da_file_name); |
| goto cleanup; |
| } |
| |
| while ((tag = gcov_read_unsigned ())) |
| { |
| unsigned length = gcov_read_unsigned (); |
| unsigned long base = gcov_position (); |
| |
| if (tag == GCOV_TAG_OBJECT_SUMMARY) |
| gcov_read_summary (&object_summary); |
| else if (tag == GCOV_TAG_PROGRAM_SUMMARY) |
| program_count++; |
| else if (tag == GCOV_TAG_FUNCTION) |
| { |
| unsigned ident = gcov_read_unsigned (); |
| struct function_info *fn_n = functions; |
| |
| for (fn = fn ? fn->next : NULL; ; fn = fn->next) |
| { |
| if (fn) |
| ; |
| else if ((fn = fn_n)) |
| fn_n = NULL; |
| else |
| { |
| fnotice (stderr, "%s:unknown function '%u'\n", |
| da_file_name, ident); |
| break; |
| } |
| if (fn->ident == ident) |
| break; |
| } |
| |
| if (!fn) |
| ; |
| else if (gcov_read_unsigned () != fn->checksum) |
| { |
| mismatch:; |
| fnotice (stderr, "%s:profile mismatch for '%s'\n", |
| da_file_name, fn->name); |
| goto cleanup; |
| } |
| } |
| else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn) |
| { |
| if (length != GCOV_TAG_COUNTER_LENGTH (fn->num_counts)) |
| goto mismatch; |
| |
| if (!fn->counts) |
| fn->counts = xcalloc (fn->num_counts, sizeof (gcov_type)); |
| |
| for (ix = 0; ix != fn->num_counts; ix++) |
| fn->counts[ix] += gcov_read_counter (); |
| } |
| gcov_sync (base, length); |
| if ((error = gcov_is_error ())) |
| { |
| fnotice (stderr, error < 0 ? "%s:overflowed\n" : "%s:corrupted\n", |
| da_file_name); |
| goto cleanup; |
| } |
| } |
| |
| gcov_close (); |
| return 0; |
| } |
| |
| /* Solve the flow graph. Propagate counts from the instrumented arcs |
| to the blocks and the uninstrumented arcs. */ |
| |
| static void |
| solve_flow_graph (function_t *fn) |
| { |
| unsigned ix; |
| arc_t *arc; |
| gcov_type *count_ptr = fn->counts; |
| block_t *blk; |
| block_t *valid_blocks = NULL; /* valid, but unpropagated blocks. */ |
| block_t *invalid_blocks = NULL; /* invalid, but inferable blocks. */ |
| |
| if (fn->num_blocks < 2) |
| fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n", |
| bbg_file_name, fn->name); |
| else |
| { |
| if (fn->blocks[0].num_pred) |
| fnotice (stderr, "%s:'%s' has arcs to entry block\n", |
| bbg_file_name, fn->name); |
| else |
| /* We can't deduce the entry block counts from the lack of |
| predecessors. */ |
| fn->blocks[0].num_pred = ~(unsigned)0; |
| |
| if (fn->blocks[fn->num_blocks - 1].num_succ) |
| fnotice (stderr, "%s:'%s' has arcs from exit block\n", |
| bbg_file_name, fn->name); |
| else |
| /* Likewise, we can't deduce exit block counts from the lack |
| of its successors. */ |
| fn->blocks[fn->num_blocks - 1].num_succ = ~(unsigned)0; |
| } |
| |
| /* Propagate the measured counts, this must be done in the same |
| order as the code in profile.c */ |
| for (ix = 0, blk = fn->blocks; ix != fn->num_blocks; ix++, blk++) |
| { |
| block_t const *prev_dst = NULL; |
| int out_of_order = 0; |
| int non_fake_succ = 0; |
| |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| { |
| if (!arc->fake) |
| non_fake_succ++; |
| |
| if (!arc->on_tree) |
| { |
| if (count_ptr) |
| arc->count = *count_ptr++; |
| arc->count_valid = 1; |
| blk->num_succ--; |
| arc->dst->num_pred--; |
| } |
| if (prev_dst && prev_dst > arc->dst) |
| out_of_order = 1; |
| prev_dst = arc->dst; |
| } |
| if (non_fake_succ == 1) |
| { |
| /* If there is only one non-fake exit, it is an |
| unconditional branch. */ |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| if (!arc->fake) |
| { |
| arc->is_unconditional = 1; |
| /* If this block is instrumenting a call, it might be |
| an artificial block. It is not artificial if it has |
| a non-fallthrough exit, or the destination of this |
| arc has more than one entry. Mark the destination |
| block as a return site, if none of those conditions |
| hold. */ |
| if (blk->is_call_site && arc->fall_through |
| && arc->dst->pred == arc && !arc->pred_next) |
| arc->dst->is_call_return = 1; |
| } |
| } |
| |
| /* Sort the successor arcs into ascending dst order. profile.c |
| normally produces arcs in the right order, but sometimes with |
| one or two out of order. We're not using a particularly |
| smart sort. */ |
| if (out_of_order) |
| { |
| arc_t *start = blk->succ; |
| unsigned changes = 1; |
| |
| while (changes) |
| { |
| arc_t *arc, *arc_p, *arc_n; |
| |
| changes = 0; |
| for (arc_p = NULL, arc = start; (arc_n = arc->succ_next);) |
| { |
| if (arc->dst > arc_n->dst) |
| { |
| changes = 1; |
| if (arc_p) |
| arc_p->succ_next = arc_n; |
| else |
| start = arc_n; |
| arc->succ_next = arc_n->succ_next; |
| arc_n->succ_next = arc; |
| arc_p = arc_n; |
| } |
| else |
| { |
| arc_p = arc; |
| arc = arc_n; |
| } |
| } |
| } |
| blk->succ = start; |
| } |
| |
| /* Place it on the invalid chain, it will be ignored if that's |
| wrong. */ |
| blk->invalid_chain = 1; |
| blk->chain = invalid_blocks; |
| invalid_blocks = blk; |
| } |
| |
| while (invalid_blocks || valid_blocks) |
| { |
| while ((blk = invalid_blocks)) |
| { |
| gcov_type total = 0; |
| const arc_t *arc; |
| |
| invalid_blocks = blk->chain; |
| blk->invalid_chain = 0; |
| if (!blk->num_succ) |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| total += arc->count; |
| else if (!blk->num_pred) |
| for (arc = blk->pred; arc; arc = arc->pred_next) |
| total += arc->count; |
| else |
| continue; |
| |
| blk->count = total; |
| blk->count_valid = 1; |
| blk->chain = valid_blocks; |
| blk->valid_chain = 1; |
| valid_blocks = blk; |
| } |
| while ((blk = valid_blocks)) |
| { |
| gcov_type total; |
| arc_t *arc, *inv_arc; |
| |
| valid_blocks = blk->chain; |
| blk->valid_chain = 0; |
| if (blk->num_succ == 1) |
| { |
| block_t *dst; |
| |
| total = blk->count; |
| inv_arc = NULL; |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| { |
| total -= arc->count; |
| if (!arc->count_valid) |
| inv_arc = arc; |
| } |
| dst = inv_arc->dst; |
| inv_arc->count_valid = 1; |
| inv_arc->count = total; |
| blk->num_succ--; |
| dst->num_pred--; |
| if (dst->count_valid) |
| { |
| if (dst->num_pred == 1 && !dst->valid_chain) |
| { |
| dst->chain = valid_blocks; |
| dst->valid_chain = 1; |
| valid_blocks = dst; |
| } |
| } |
| else |
| { |
| if (!dst->num_pred && !dst->invalid_chain) |
| { |
| dst->chain = invalid_blocks; |
| dst->invalid_chain = 1; |
| invalid_blocks = dst; |
| } |
| } |
| } |
| if (blk->num_pred == 1) |
| { |
| block_t *src; |
| |
| total = blk->count; |
| inv_arc = NULL; |
| for (arc = blk->pred; arc; arc = arc->pred_next) |
| { |
| total -= arc->count; |
| if (!arc->count_valid) |
| inv_arc = arc; |
| } |
| src = inv_arc->src; |
| inv_arc->count_valid = 1; |
| inv_arc->count = total; |
| blk->num_pred--; |
| src->num_succ--; |
| if (src->count_valid) |
| { |
| if (src->num_succ == 1 && !src->valid_chain) |
| { |
| src->chain = valid_blocks; |
| src->valid_chain = 1; |
| valid_blocks = src; |
| } |
| } |
| else |
| { |
| if (!src->num_succ && !src->invalid_chain) |
| { |
| src->chain = invalid_blocks; |
| src->invalid_chain = 1; |
| invalid_blocks = src; |
| } |
| } |
| } |
| } |
| } |
| |
| /* If the graph has been correctly solved, every block will have a |
| valid count. */ |
| for (ix = 0; ix < fn->num_blocks; ix++) |
| if (!fn->blocks[ix].count_valid) |
| { |
| fnotice (stderr, "%s:graph is unsolvable for '%s'\n", |
| bbg_file_name, fn->name); |
| break; |
| } |
| } |
| |
| |
| |
| /* Increment totals in COVERAGE according to arc ARC. */ |
| |
| static void |
| add_branch_counts (coverage_t *coverage, const arc_t *arc) |
| { |
| if (arc->is_call_non_return) |
| { |
| coverage->calls++; |
| if (arc->src->count) |
| coverage->calls_executed++; |
| } |
| else if (!arc->is_unconditional) |
| { |
| coverage->branches++; |
| if (arc->src->count) |
| coverage->branches_executed++; |
| if (arc->count) |
| coverage->branches_taken++; |
| } |
| } |
| |
| /* 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_gcov (gcov_type top, gcov_type 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 + (float)0.5); |
| if (percent <= 0 && 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, (HOST_WIDEST_INT)top); |
| |
| return buffer; |
| } |
| |
| |
| /* Output summary info for a function. */ |
| |
| static void |
| function_summary (const coverage_t *coverage, const char *title) |
| { |
| fnotice (stdout, "%s '%s'\n", title, coverage->name); |
| |
| if (coverage->lines) |
| fnotice (stdout, "Lines executed:%s of %d\n", |
| format_gcov (coverage->lines_executed, coverage->lines, 2), |
| coverage->lines); |
| else |
| fnotice (stdout, "No executable lines\n"); |
| |
| if (flag_branches) |
| { |
| if (coverage->branches) |
| { |
| fnotice (stdout, "Branches executed:%s of %d\n", |
| format_gcov (coverage->branches_executed, |
| coverage->branches, 2), |
| coverage->branches); |
| fnotice (stdout, "Taken at least once:%s of %d\n", |
| format_gcov (coverage->branches_taken, |
| coverage->branches, 2), |
| coverage->branches); |
| } |
| else |
| fnotice (stdout, "No branches\n"); |
| if (coverage->calls) |
| fnotice (stdout, "Calls executed:%s of %d\n", |
| format_gcov (coverage->calls_executed, coverage->calls, 2), |
| coverage->calls); |
| else |
| fnotice (stdout, "No calls\n"); |
| } |
| } |
| |
| /* 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 '^'. */ |
| |
| static char * |
| make_gcov_file_name (const char *input_name, const char *src_name) |
| { |
| char *cptr; |
| char *name = xmalloc (strlen (src_name) + strlen (input_name) + 10); |
| |
| name[0] = 0; |
| if (flag_long_names && strcmp (src_name, input_name)) |
| { |
| /* Generate the input filename part. */ |
| cptr = flag_preserve_paths ? NULL : strrchr (input_name, '/'); |
| strcat (name, cptr ? cptr + 1 : input_name); |
| strcat (name, "##"); |
| } |
| |
| /* Generate the source filename part. */ |
| cptr = flag_preserve_paths ? NULL : strrchr (src_name, '/'); |
| strcat (name, cptr ? cptr + 1 : src_name); |
| |
| if (flag_preserve_paths) |
| { |
| /* Convert '/' to '#', remove '/./', convert '/../' to '/^/' */ |
| char *prev; |
| |
| for (cptr = 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++); |
| } |
| } |
| } |
| |
| strcat (name, ".gcov"); |
| return name; |
| } |
| |
| /* Scan through the bb_data for each line in the block, increment |
| the line number execution count indicated by the execution count of |
| the appropriate basic block. */ |
| |
| static void |
| add_line_counts (coverage_t *coverage, function_t *fn) |
| { |
| unsigned ix; |
| line_t *line = NULL; /* This is propagated from one iteration to the |
| next. */ |
| |
| /* Scan each basic block. */ |
| for (ix = 0; ix != fn->num_blocks; ix++) |
| { |
| block_t *block = &fn->blocks[ix]; |
| unsigned *encoding; |
| const source_t *src = NULL; |
| unsigned jx; |
| |
| if (block->count && ix && ix + 1 != fn->num_blocks) |
| fn->blocks_executed++; |
| for (jx = 0, encoding = block->u.line.encoding; |
| jx != block->u.line.num; jx++, encoding++) |
| if (!*encoding) |
| { |
| unsigned src_n = *++encoding; |
| |
| for (src = sources; src->index != src_n; src = src->next) |
| continue; |
| jx++; |
| } |
| else |
| { |
| line = &src->lines[*encoding]; |
| |
| if (coverage) |
| { |
| if (!line->exists) |
| coverage->lines++; |
| if (!line->count && block->count) |
| coverage->lines_executed++; |
| } |
| line->exists = 1; |
| line->count += block->count; |
| } |
| free (block->u.line.encoding); |
| block->u.cycle.arc = NULL; |
| block->u.cycle.ident = ~0U; |
| |
| if (!ix || ix + 1 == fn->num_blocks) |
| /* Entry or exit block */; |
| else if (flag_all_blocks) |
| { |
| line_t *block_line = line ? line : &fn->src->lines[fn->line]; |
| |
| block->chain = block_line->u.blocks; |
| block_line->u.blocks = block; |
| } |
| else if (flag_branches) |
| { |
| arc_t *arc; |
| |
| for (arc = block->succ; arc; arc = arc->succ_next) |
| { |
| arc->line_next = line->u.branches; |
| line->u.branches = arc; |
| if (coverage && !arc->is_unconditional) |
| add_branch_counts (coverage, arc); |
| } |
| } |
| } |
| if (!line) |
| fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name); |
| } |
| |
| /* Accumulate the line counts of a file. */ |
| |
| static void |
| accumulate_line_counts (source_t *src) |
| { |
| line_t *line; |
| function_t *fn, *fn_p, *fn_n; |
| unsigned ix; |
| |
| /* Reverse the function order. */ |
| for (fn = src->functions, fn_p = NULL; fn; |
| fn_p = fn, fn = fn_n) |
| { |
| fn_n = fn->line_next; |
| fn->line_next = fn_p; |
| } |
| src->functions = fn_p; |
| |
| for (ix = src->num_lines, line = src->lines; ix--; line++) |
| { |
| if (!flag_all_blocks) |
| { |
| arc_t *arc, *arc_p, *arc_n; |
| |
| /* Total and reverse the branch information. */ |
| for (arc = line->u.branches, arc_p = NULL; arc; |
| arc_p = arc, arc = arc_n) |
| { |
| arc_n = arc->line_next; |
| arc->line_next = arc_p; |
| |
| add_branch_counts (&src->coverage, arc); |
| } |
| line->u.branches = arc_p; |
| } |
| else if (line->u.blocks) |
| { |
| /* 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. */ |
| block_t *block, *block_p, *block_n; |
| gcov_type count = 0; |
| |
| /* Reverse the block information. */ |
| for (block = line->u.blocks, block_p = NULL; block; |
| block_p = block, block = block_n) |
| { |
| block_n = block->chain; |
| block->chain = block_p; |
| block->u.cycle.ident = ix; |
| } |
| line->u.blocks = block_p; |
| |
| /* Sum the entry arcs. */ |
| for (block = line->u.blocks; block; block = block->chain) |
| { |
| arc_t *arc; |
| |
| for (arc = block->pred; arc; arc = arc->pred_next) |
| { |
| if (arc->src->u.cycle.ident != ix) |
| count += arc->count; |
| if (flag_branches) |
| add_branch_counts (&src->coverage, arc); |
| } |
| |
| /* Initialize the cs_count. */ |
| for (arc = block->succ; arc; arc = arc->succ_next) |
| arc->cs_count = arc->count; |
| } |
| |
| /* Find the loops. This uses the algorithm described in |
| Tiernan 'An Efficient Search Algorithm to Find the |
| Elementary Circuits of a Graph', CACM Dec 1970. We hold |
| the P array by having each block point to the arc that |
| connects to the previous block. The H array is implicitly |
| held because of the arc ordering, and the block's |
| previous arc pointer. |
| |
| Although the algorithm is O(N^3) for highly connected |
| graphs, at worst we'll have O(N^2), as most blocks have |
| only one or two exits. Most graphs will be small. |
| |
| For each loop we find, locate the arc with the smallest |
| transition count, and add that to the cumulative |
| count. Decrease flow over the cycle and remove the arc |
| from consideration. */ |
| for (block = line->u.blocks; block; block = block->chain) |
| { |
| block_t *head = block; |
| arc_t *arc; |
| |
| next_vertex:; |
| arc = head->succ; |
| current_vertex:; |
| while (arc) |
| { |
| block_t *dst = arc->dst; |
| if (/* Already used that arc. */ |
| arc->cycle |
| /* Not to same graph, or before first vertex. */ |
| || dst->u.cycle.ident != ix |
| /* Already in path. */ |
| || dst->u.cycle.arc) |
| { |
| arc = arc->succ_next; |
| continue; |
| } |
| |
| if (dst == block) |
| { |
| /* Found a closing arc. */ |
| gcov_type cycle_count = arc->cs_count; |
| arc_t *cycle_arc = arc; |
| arc_t *probe_arc; |
| |
| /* Locate the smallest arc count of the loop. */ |
| for (dst = head; (probe_arc = dst->u.cycle.arc); |
| dst = probe_arc->src) |
| if (cycle_count > probe_arc->cs_count) |
| { |
| cycle_count = probe_arc->cs_count; |
| cycle_arc = probe_arc; |
| } |
| |
| count += cycle_count; |
| cycle_arc->cycle = 1; |
| |
| /* Remove the flow from the cycle. */ |
| arc->cs_count -= cycle_count; |
| for (dst = head; (probe_arc = dst->u.cycle.arc); |
| dst = probe_arc->src) |
| probe_arc->cs_count -= cycle_count; |
| |
| /* Unwind to the cyclic arc. */ |
| while (head != cycle_arc->src) |
| { |
| arc = head->u.cycle.arc; |
| head->u.cycle.arc = NULL; |
| head = arc->src; |
| } |
| /* Move on. */ |
| arc = arc->succ_next; |
| continue; |
| } |
| |
| /* Add new block to chain. */ |
| dst->u.cycle.arc = arc; |
| head = dst; |
| goto next_vertex; |
| } |
| /* We could not add another vertex to the path. Remove |
| the last vertex from the list. */ |
| arc = head->u.cycle.arc; |
| if (arc) |
| { |
| /* It was not the first vertex. Move onto next arc. */ |
| head->u.cycle.arc = NULL; |
| head = arc->src; |
| arc = arc->succ_next; |
| goto current_vertex; |
| } |
| /* Mark this block as unusable. */ |
| block->u.cycle.ident = ~0U; |
| } |
| |
| line->count = count; |
| } |
| |
| if (line->exists) |
| { |
| src->coverage.lines++; |
| if (line->count) |
| src->coverage.lines_executed++; |
| } |
| } |
| } |
| |
| /* Output information about ARC number IX. Returns nonzero if |
| anything is output. */ |
| |
| static int |
| output_branch_count (FILE *gcov_file, int ix, const arc_t *arc) |
| { |
| |
| if (arc->is_call_non_return) |
| { |
| if (arc->src->count) |
| { |
| fnotice (gcov_file, "call %2d returned %s\n", ix, |
| format_gcov (arc->src->count - arc->count, |
| arc->src->count, -flag_counts)); |
| } |
| else |
| fnotice (gcov_file, "call %2d never executed\n", ix); |
| } |
| else if (!arc->is_unconditional) |
| { |
| if (arc->src->count) |
| fnotice (gcov_file, "branch %2d taken %s%s\n", ix, |
| format_gcov (arc->count, arc->src->count, -flag_counts), |
| arc->fall_through ? " (fallthrough)" : ""); |
| else |
| fnotice (gcov_file, "branch %2d never executed\n", ix); |
| } |
| else if (flag_unconditional && !arc->dst->is_call_return) |
| { |
| if (arc->src->count) |
| fnotice (gcov_file, "unconditional %2d taken %s\n", ix, |
| format_gcov (arc->count, arc->src->count, -flag_counts)); |
| else |
| fnotice (gcov_file, "unconditional %2d never executed\n", ix); |
| } |
| else |
| return 0; |
| return 1; |
| |
| } |
| |
| /* 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. */ |
| |
| static void |
| output_lines (FILE *gcov_file, const source_t *src) |
| { |
| FILE *source_file; |
| unsigned line_num; /* current line number. */ |
| const line_t *line; /* current line info ptr. */ |
| char string[STRING_SIZE]; /* line buffer. */ |
| char const *retval = ""; /* status of source file reading. */ |
| function_t *fn = NULL; |
| |
| fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, src->name); |
| fprintf (gcov_file, "%9s:%5d:Graph:%s\n", "-", 0, bbg_file_name); |
| fprintf (gcov_file, "%9s:%5d:Data:%s\n", "-", 0, da_file_name); |
| fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0, |
| object_summary.ctrs[GCOV_COUNTER_ARCS].runs); |
| fprintf (gcov_file, "%9s:%5d:Programs:%u\n", "-", 0, program_count); |
| |
| source_file = fopen (src->name, "r"); |
| if (!source_file) |
| { |
| fnotice (stderr, "%s:cannot open source file\n", src->name); |
| retval = NULL; |
| } |
| else |
| { |
| struct stat status; |
| |
| if (!fstat (fileno (source_file), &status) |
| && status.st_mtime > bbg_file_time) |
| { |
| fnotice (stderr, "%s:source file is newer than graph file '%s'\n", |
| src->name, bbg_file_name); |
| fprintf (gcov_file, "%9s:%5d:Source is newer than graph\n", |
| "-", 0); |
| } |
| } |
| |
| if (flag_branches) |
| fn = src->functions; |
| |
| for (line_num = 1, line = &src->lines[line_num]; |
| line_num < src->num_lines; line_num++, line++) |
| { |
| for (; fn && fn->line == line_num; fn = fn->line_next) |
| { |
| arc_t *arc = fn->blocks[fn->num_blocks - 1].pred; |
| gcov_type return_count = fn->blocks[fn->num_blocks - 1].count; |
| |
| for (; arc; arc = arc->pred_next) |
| if (arc->fake) |
| return_count -= arc->count; |
| |
| fprintf (gcov_file, "function %s", fn->name); |
| fprintf (gcov_file, " called %s", |
| format_gcov (fn->blocks[0].count, 0, -1)); |
| fprintf (gcov_file, " returned %s", |
| format_gcov (return_count, fn->blocks[0].count, 0)); |
| fprintf (gcov_file, " blocks executed %s", |
| format_gcov (fn->blocks_executed, fn->num_blocks - 2, 0)); |
| fprintf (gcov_file, "\n"); |
| } |
| |
| /* 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. */ |
| fprintf (gcov_file, "%9s:%5u:", |
| !line->exists ? "-" : !line->count ? "#####" |
| : format_gcov (line->count, 0, -1), line_num); |
| |
| if (retval) |
| { |
| /* Copy source line. */ |
| do |
| { |
| retval = fgets (string, STRING_SIZE, source_file); |
| if (!retval) |
| break; |
| fputs (retval, gcov_file); |
| } |
| while (!retval[0] || retval[strlen (retval) - 1] != '\n'); |
| } |
| if (!retval) |
| fputs ("/*EOF*/\n", gcov_file); |
| |
| if (flag_all_blocks) |
| { |
| block_t *block; |
| arc_t *arc; |
| int ix, jx; |
| |
| for (ix = jx = 0, block = line->u.blocks; block; |
| block = block->chain) |
| { |
| if (!block->is_call_return) |
| fprintf (gcov_file, "%9s:%5u-block %2d\n", |
| !line->exists ? "-" : !block->count ? "$$$$$" |
| : format_gcov (block->count, 0, -1), |
| line_num, ix++); |
| if (flag_branches) |
| for (arc = block->succ; arc; arc = arc->succ_next) |
| jx += output_branch_count (gcov_file, jx, arc); |
| } |
| } |
| else if (flag_branches) |
| { |
| int ix; |
| arc_t *arc; |
| |
| for (ix = 0, arc = line->u.branches; arc; arc = arc->line_next) |
| ix += output_branch_count (gcov_file, ix, arc); |
| } |
| } |
| |
| /* 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)); line_num++) |
| { |
| fprintf (gcov_file, "%9s:%5u:%s", "-", line_num, retval); |
| |
| while (!retval[0] || retval[strlen (retval) - 1] != '\n') |
| { |
| retval = fgets (string, STRING_SIZE, source_file); |
| if (!retval) |
| break; |
| fputs (retval, gcov_file); |
| } |
| } |
| } |
| |
| if (source_file) |
| fclose (source_file); |
| } |