diff options
author | Ben Cheng <bccheng@google.com> | 2014-03-25 22:37:19 -0700 |
---|---|---|
committer | Ben Cheng <bccheng@google.com> | 2014-03-25 22:37:19 -0700 |
commit | 1bc5aee63eb72b341f506ad058502cd0361f0d10 (patch) | |
tree | c607e8252f3405424ff15bc2d00aa38dadbb2518 /gcc-4.9/gcc/gcov.c | |
parent | 283a0bf58fcf333c58a2a92c3ebbc41fb9eb1fdb (diff) | |
download | toolchain_gcc-1bc5aee63eb72b341f506ad058502cd0361f0d10.tar.gz toolchain_gcc-1bc5aee63eb72b341f506ad058502cd0361f0d10.tar.bz2 toolchain_gcc-1bc5aee63eb72b341f506ad058502cd0361f0d10.zip |
Initial checkin of GCC 4.9.0 from trunk (r208799).
Change-Id: I48a3c08bb98542aa215912a75f03c0890e497dba
Diffstat (limited to 'gcc-4.9/gcc/gcov.c')
-rw-r--r-- | gcc-4.9/gcc/gcov.c | 2512 |
1 files changed, 2512 insertions, 0 deletions
diff --git a/gcc-4.9/gcc/gcov.c b/gcc-4.9/gcc/gcov.c new file mode 100644 index 000000000..b0e59e853 --- /dev/null +++ b/gcc-4.9/gcc/gcov.c @@ -0,0 +1,2512 @@ +/* Gcov.c: prepend line execution counts and branch probabilities to a + source file. + Copyright (C) 1990-2014 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 3, 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 COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +/* ??? 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. */ + +/* 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 "diagnostic.h" +#include "version.h" +#include "demangle.h" + +#include <getopt.h> + +#define IN_GCOV 1 +#include "gcov-io.h" +#include "gcov-io.c" + +/* The gcno file is generated by -ftest-coverage option. The gcda 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. */ + +/* The code validates that the profile information read in corresponds + to the code currently being compiled. Rather than checking for + identical files, the code below compares a checksum on the CFG + (based on the order of basic blocks and the arcs in the CFG). If + the CFG checksum in the gcda file match the CFG checksum in the + gcno file, the profile data will be used. */ + +/* This is the size of the buffer used to read in source file lines. */ + +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 to a catch handler. */ + unsigned int is_throw : 1; + + /* Arc is for a function that abnormally returns. */ + unsigned int is_call_non_return : 1; + + /* Arc is for catch/setjmp. */ + 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 : 12; + unsigned count_valid : 1; + unsigned valid_chain : 1; + unsigned invalid_chain : 1; + unsigned exceptional : 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; + char *demangled_name; + unsigned ident; + unsigned lineno_checksum; + unsigned cfg_checksum; + + /* The graph contains at least one fake incoming edge. */ + unsigned has_catch : 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) +#define EXIT_BLOCK (1) + block_t *blocks; + unsigned num_blocks; + unsigned blocks_executed; + + /* Raw arc coverage counts. */ + gcov_type *counts; + unsigned num_counts; + + /* First line number & file. */ + unsigned line; + unsigned 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; + unsigned unexceptional : 1; +} line_t; + +/* Describes a file mentioned in the block graph. Contains an array + of line info. */ + +typedef struct source_info +{ + /* Canonical name of source file. */ + char *name; + time_t file_time; + + /* 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; +} source_t; + +typedef struct name_map +{ + char *name; /* Source file name */ + unsigned src; /* Source file */ +} name_map_t; + +/* Holds a list of function basic block graphs. */ + +static function_t *functions; +static function_t **fn_end = &functions; + +static source_t *sources; /* Array of source files */ +static unsigned n_sources; /* Number of sources */ +static unsigned a_sources; /* Allocated sources */ + +static name_map_t *names; /* Mapping of file names to sources */ +static unsigned n_names; /* Number of names */ +static unsigned a_names; /* Allocated names */ + +/* This holds data summary information. */ + +static unsigned object_runs; +static unsigned program_count; + +static unsigned total_lines; +static unsigned total_executed; + +/* Modification time of graph file. */ + +static time_t bbg_file_time; + +/* Name of the notes (gcno) output file. The "bbg" prefix is for + historical reasons, when the notes file contained only the + basic block graph notes. */ + +static char *bbg_file_name; + +/* Stamp of the bbg file */ +static unsigned bbg_stamp; + +/* Name and file pointer of the input file for the count data (gcda). */ + +static char *da_file_name; + +/* Data file is missing. */ + +static int no_data_file; + +/* If there is several input files, compute and display results after + reading all data files. This way if two or more gcda file refer to + the same source file (eg inline subprograms in a .h file), the + counts are added. */ + +static int multiple_files = 0; + +/* 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; + +/* Output progress indication if this is true. This is off by default + and can be turned on by the -d option. */ + +static int flag_display_progress = 0; + +/* Output *.gcov file in intermediate format used by 'lcov'. */ + +static int flag_intermediate_format = 0; + +/* Output demangled function names. */ + +static int flag_demangled_names = 0; + +/* 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; + +/* Source directory prefix. This is removed from source pathnames + that match, when generating the output file name. */ + +static char *source_prefix = 0; +static size_t source_length = 0; + +/* Only show data for sources with relative pathnames. Absolute ones + usually indicate a system header file, which although it may + contain inline functions, is usually uninteresting. */ +static int flag_relative_only = 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 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 generate_results (const char *); +static void create_file_names (const char *); +static int name_search (const void *, const void *); +static int name_sort (const void *, const void *); +static char *canonicalize_name (const char *); +static unsigned find_source (const char *); +static function_t *read_graph_file (void); +static int read_count_file (function_t *); +static void solve_flow_graph (function_t *); +static void find_exception_blocks (function_t *); +static void add_branch_counts (coverage_t *, const arc_t *); +static void add_line_counts (coverage_t *, function_t *); +static void executed_summary (unsigned, unsigned); +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 void output_gcov_file (const char *, 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 char *mangle_name (const char *, char *); +static void release_structures (void); +static void release_function (function_t *); +extern int main (int, char **); + +int +main (int argc, char **argv) +{ + int argno; + int first_arg; + const char *p; + + p = argv[0] + strlen (argv[0]); + while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1])) + --p; + progname = p; + + xmalloc_set_program_name (progname); + + /* Unlock the stdio streams. */ + unlock_std_streams (); + + gcc_init_libintl (); + + diagnostic_initialize (global_dc, 0); + + /* Handle response files. */ + expandargv (&argc, &argv); + + a_names = 10; + names = XNEWVEC (name_map_t, a_names); + a_sources = 10; + sources = XNEWVEC (source_t, a_sources); + + argno = process_args (argc, argv); + if (optind == argc) + print_usage (true); + + if (argc - argno > 1) + multiple_files = 1; + + first_arg = argno; + + for (; argno != argc; argno++) + { + if (flag_display_progress) + printf ("Processing file %d out of %d\n", + argno - first_arg + 1, argc - first_arg); + process_file (argv[argno]); + } + + generate_results (multiple_files ? NULL : argv[argc - 1]); + + release_structures (); + + return 0; +} + +/* 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]... SOURCE|OBJ...\n\n"); + fnotice (file, "Print code coverage information.\n\n"); + fnotice (file, " -h, --help Print this help, 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 Output counts of branches taken\n\ + rather than percentages\n"); + fnotice (file, " -d, --display-progress Display progress information\n"); + fnotice (file, " -f, --function-summaries Output summaries for each function\n"); + fnotice (file, " -i, --intermediate-format Output .gcov file in intermediate text format\n"); + fnotice (file, " -l, --long-file-names Use long output file names for included\n\ + source files\n"); + fnotice (file, " -m, --demangled-names Output demangled function names\n"); + fnotice (file, " -n, --no-output Do not create an output file\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, " -r, --relative-only Only show data for relative sources\n"); + fnotice (file, " -s, --source-prefix DIR Source prefix to elide\n"); + fnotice (file, " -u, --unconditional-branches Show unconditional branch counts too\n"); + fnotice (file, " -v, --version Print version number, then exit\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 %s%s\n", pkgversion_string, version_string); + fprintf (stdout, "Copyright %s 2014 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' }, + { "intermediate-format", no_argument, NULL, 'i' }, + { "no-output", no_argument, NULL, 'n' }, + { "long-file-names", no_argument, NULL, 'l' }, + { "function-summaries", no_argument, NULL, 'f' }, + { "demangled-names", no_argument, NULL, 'm' }, + { "preserve-paths", no_argument, NULL, 'p' }, + { "relative-only", no_argument, NULL, 'r' }, + { "object-directory", required_argument, NULL, 'o' }, + { "object-file", required_argument, NULL, 'o' }, + { "source-prefix", required_argument, NULL, 's' }, + { "unconditional-branches", no_argument, NULL, 'u' }, + { "display-progress", no_argument, NULL, 'd' }, + { 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, "abcdfhilmno:s:pruv", 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 'm': + flag_demangled_names = 1; + break; + case 'n': + flag_gcov_file = 0; + break; + case 'o': + object_directory = optarg; + break; + case 's': + source_prefix = optarg; + source_length = strlen (source_prefix); + break; + case 'r': + flag_relative_only = 1; + break; + case 'p': + flag_preserve_paths = 1; + break; + case 'u': + flag_unconditional = 1; + break; + case 'i': + flag_intermediate_format = 1; + flag_gcov_file = 1; + break; + case 'd': + flag_display_progress = 1; + break; + case 'v': + print_version (); + /* print_version will exit. */ + default: + print_usage (true); + /* print_usage will exit. */ + } + } + + return optind; +} + +/* Get the name of the gcov file. The return value must be free'd. + + It appends the '.gcov' extension to the *basename* of the file. + The resulting file name will be in PWD. + + e.g., + input: foo.da, output: foo.da.gcov + input: a/b/foo.cc, output: foo.cc.gcov */ + +static char * +get_gcov_intermediate_filename (const char *file_name) +{ + const char *gcov = ".gcov"; + char *result; + const char *cptr; + + /* Find the 'basename'. */ + cptr = lbasename (file_name); + + result = XNEWVEC (char, strlen (cptr) + strlen (gcov) + 1); + sprintf (result, "%s%s", cptr, gcov); + + return result; +} + +/* Output the result in intermediate format used by 'lcov'. + +The intermediate format contains a single file named 'foo.cc.gcov', +with no source code included. A sample output is + +file:foo.cc +function:5,1,_Z3foov +function:13,1,main +function:19,1,_GLOBAL__sub_I__Z3foov +function:19,1,_Z41__static_initialization_and_destruction_0ii +lcount:5,1 +lcount:7,9 +lcount:9,8 +lcount:11,1 +file:/.../iostream +lcount:74,1 +file:/.../basic_ios.h +file:/.../ostream +file:/.../ios_base.h +function:157,0,_ZStorSt12_Ios_IostateS_ +lcount:157,0 +file:/.../char_traits.h +function:258,0,_ZNSt11char_traitsIcE6lengthEPKc +lcount:258,0 +... + +The default gcov outputs multiple files: 'foo.cc.gcov', +'iostream.gcov', 'ios_base.h.gcov', etc. with source code +included. Instead the intermediate format here outputs only a single +file 'foo.cc.gcov' similar to the above example. */ + +static void +output_intermediate_file (FILE *gcov_file, source_t *src) +{ + unsigned line_num; /* current line number. */ + const line_t *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->line_next) + { + /* function:<name>,<line_number>,<execution_count> */ + 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); + } + + for (line_num = 1, line = &src->lines[line_num]; + line_num < src->num_lines; + line_num++, line++) + { + arc_t *arc; + if (line->exists) + fprintf (gcov_file, "lcount:%u,%s\n", line_num, + format_gcov (line->count, 0, -1)); + if (flag_branches) + for (arc = line->u.branches; arc; arc = arc->line_next) + { + if (!arc->is_unconditional && !arc->is_call_non_return) + { + const char *branch_type; + /* branch:<line_num>,<branch_coverage_type> + branch_coverage_type + : notexec (Branch not executed) + : taken (Branch executed and taken) + : nottaken (Branch executed, but not taken) + */ + if (arc->src->count) + branch_type = (arc->count > 0) ? "taken" : "nottaken"; + else + branch_type = "notexec"; + fprintf (gcov_file, "branch:%d,%s\n", line_num, branch_type); + } + } + } +} + + +/* Process a single input file. */ + +static void +process_file (const char *file_name) +{ + function_t *fns; + + create_file_names (file_name); + fns = read_graph_file (); + if (!fns) + return; + + read_count_file (fns); + while (fns) + { + function_t *fn = fns; + + fns = fn->next; + fn->next = NULL; + if (fn->counts) + { + 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->line_next) + if (probe->line <= line) + break; + fn->line_next = probe; + *prev = fn; + + /* Mark last line in files touched by function. */ + for (block_no = 0; block_no != fn->num_blocks; block_no++) + { + unsigned *enc = fn->blocks[block_no].u.line.encoding; + unsigned num = fn->blocks[block_no].u.line.num; + + for (; num--; enc++) + if (!*enc) + { + if (enc[1] != src) + { + if (line >= sources[src].num_lines) + sources[src].num_lines = line + 1; + line = 0; + src = enc[1]; + } + enc++; + num--; + } + else if (*enc > line) + line = *enc; + } + if (line >= sources[src].num_lines) + sources[src].num_lines = line + 1; + + solve_flow_graph (fn); + if (fn->has_catch) + find_exception_blocks (fn); + *fn_end = fn; + fn_end = &fn->next; + } + else + /* The function was not in the executable -- some other + instance must have been selected. */ + release_function (fn); + } +} + +static void +output_gcov_file (const char *file_name, source_t *src) +{ + char *gcov_file_name = make_gcov_file_name (file_name, src->coverage.name); + + if (src->coverage.lines) + { + FILE *gcov_file = fopen (gcov_file_name, "w"); + if (gcov_file) + { + fnotice (stdout, "Creating '%s'\n", gcov_file_name); + output_lines (gcov_file, src); + if (ferror (gcov_file)) + fnotice (stderr, "Error writing output file '%s'\n", gcov_file_name); + fclose (gcov_file); + } + else + fnotice (stderr, "Could not open output file '%s'\n", gcov_file_name); + } + else + { + unlink (gcov_file_name); + fnotice (stdout, "Removing '%s'\n", gcov_file_name); + } + free (gcov_file_name); +} + +static void +generate_results (const char *file_name) +{ + unsigned ix; + source_t *src; + function_t *fn; + FILE *gcov_intermediate_file = NULL; + char *gcov_intermediate_filename = NULL; + + for (ix = n_sources, src = sources; ix--; src++) + if (src->num_lines) + src->lines = XCNEWVEC (line_t, src->num_lines); + + for (fn = functions; fn; fn = fn->next) + { + coverage_t coverage; + + memset (&coverage, 0, sizeof (coverage)); + coverage.name = flag_demangled_names ? fn->demangled_name : fn->name; + add_line_counts (flag_function_summary ? &coverage : NULL, fn); + if (flag_function_summary) + { + function_summary (&coverage, "Function"); + fnotice (stdout, "\n"); + } + } + + if (file_name) + { + name_map_t *name_map = (name_map_t *)bsearch + (file_name, names, n_names, sizeof (*names), name_search); + if (name_map) + file_name = sources[name_map->src].coverage.name; + else + file_name = canonicalize_name (file_name); + } + + if (flag_gcov_file && flag_intermediate_format) + { + /* Open the intermediate file. */ + gcov_intermediate_filename = + get_gcov_intermediate_filename (file_name); + gcov_intermediate_file = fopen (gcov_intermediate_filename, "w"); + if (!gcov_intermediate_file) + { + fnotice (stderr, "Cannot open intermediate output file %s\n", + gcov_intermediate_filename); + return; + } + } + + for (ix = n_sources, src = sources; ix--; src++) + { + if (flag_relative_only) + { + /* Ignore this source, if it is an absolute path (after + source prefix removal). */ + char first = src->coverage.name[0]; + +#if HAVE_DOS_BASED_FILE_SYSTEM + if (first && src->coverage.name[1] == ':') + first = src->coverage.name[2]; +#endif + if (IS_DIR_SEPARATOR (first)) + continue; + } + + accumulate_line_counts (src); + function_summary (&src->coverage, "File"); + total_lines += src->coverage.lines; + total_executed += src->coverage.lines_executed; + if (flag_gcov_file) + { + if (flag_intermediate_format) + /* Output the intermediate format without requiring source + files. This outputs a section to a *single* file. */ + output_intermediate_file (gcov_intermediate_file, src); + else + output_gcov_file (file_name, src); + fnotice (stdout, "\n"); + } + } + + if (flag_gcov_file && flag_intermediate_format) + { + /* Now we've finished writing the intermediate file. */ + fclose (gcov_intermediate_file); + XDELETEVEC (gcov_intermediate_filename); + } + + if (!file_name) + executed_summary (total_lines, total_executed); +} + +/* Release a function structure */ + +static void +release_function (function_t *fn) +{ + unsigned ix; + block_t *block; + + 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); + if (flag_demangled_names && fn->demangled_name != fn->name) + free (fn->demangled_name); + free (fn->name); +} + +/* Release all memory used. */ + +static void +release_structures (void) +{ + unsigned ix; + function_t *fn; + + for (ix = n_sources; ix--;) + free (sources[ix].lines); + free (sources); + + for (ix = n_names; ix--;) + free (names[ix].name); + free (names); + + while ((fn = functions)) + { + functions = fn->next; + release_function (fn); + } +} + +/* Generate the names of the graph and data files. If OBJECT_DIRECTORY + is not specified, these are named from 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; + + /* Free previous file names. */ + free (bbg_file_name); + free (da_file_name); + da_file_name = bbg_file_name = NULL; + bbg_file_time = 0; + bbg_stamp = 0; + + if (object_directory && object_directory[0]) + { + struct stat status; + + length += strlen (object_directory) + 2; + name = XNEWVEC (char, length); + name[0] = 0; + + base = !stat (object_directory, &status) && S_ISDIR (status.st_mode); + strcat (name, object_directory); + if (base && (! IS_DIR_SEPARATOR (name[strlen (name) - 1]))) + strcat (name, "/"); + } + else + { + name = XNEWVEC (char, length + 1); + strcpy (name, file_name); + base = 0; + } + + if (base) + { + /* Append source file name. */ + const char *cptr = lbasename (file_name); + strcat (name, cptr ? cptr : file_name); + } + + /* Remove the extension. */ + cptr = strrchr (CONST_CAST (char *, lbasename (name)), '.'); + if (cptr) + *cptr = 0; + + length = strlen (name); + + bbg_file_name = XNEWVEC (char, length + strlen (GCOV_NOTE_SUFFIX) + 1); + strcpy (bbg_file_name, name); + strcpy (bbg_file_name + length, GCOV_NOTE_SUFFIX); + + da_file_name = XNEWVEC (char, length + strlen (GCOV_DATA_SUFFIX) + 1); + strcpy (da_file_name, name); + strcpy (da_file_name + length, GCOV_DATA_SUFFIX); + + free (name); + return; +} + +/* A is a string and B is a pointer to name_map_t. Compare for file + name orderability. */ + +static int +name_search (const void *a_, const void *b_) +{ + const char *a = (const char *)a_; + const name_map_t *b = (const name_map_t *)b_; + +#if HAVE_DOS_BASED_FILE_SYSTEM + return strcasecmp (a, b->name); +#else + return strcmp (a, b->name); +#endif +} + +/* A and B are a pointer to name_map_t. Compare for file name + orderability. */ + +static int +name_sort (const void *a_, const void *b_) +{ + const name_map_t *a = (const name_map_t *)a_; + return name_search (a->name, b_); +} + +/* Find or create a source file structure for FILE_NAME. Copies + FILE_NAME on creation */ + +static unsigned +find_source (const char *file_name) +{ + name_map_t *name_map; + char *canon; + unsigned idx; + struct stat status; + + if (!file_name) + file_name = "<unknown>"; + name_map = (name_map_t *)bsearch + (file_name, names, n_names, sizeof (*names), name_search); + if (name_map) + { + idx = name_map->src; + goto check_date; + } + + if (n_names + 2 > a_names) + { + /* Extend the name map array -- we'll be inserting one or two + entries. */ + a_names *= 2; + name_map = XNEWVEC (name_map_t, a_names); + memcpy (name_map, names, n_names * sizeof (*names)); + free (names); + names = name_map; + } + + /* Not found, try the canonical name. */ + canon = canonicalize_name (file_name); + name_map = (name_map_t *)bsearch + (canon, names, n_names, sizeof (*names), name_search); + if (!name_map) + { + /* Not found with canonical name, create a new source. */ + source_t *src; + + if (n_sources == a_sources) + { + a_sources *= 2; + src = XNEWVEC (source_t, a_sources); + memcpy (src, sources, n_sources * sizeof (*sources)); + free (sources); + sources = src; + } + + idx = n_sources; + + name_map = &names[n_names++]; + name_map->name = canon; + name_map->src = idx; + + src = &sources[n_sources++]; + memset (src, 0, sizeof (*src)); + src->name = canon; + src->coverage.name = src->name; + if (source_length +#if HAVE_DOS_BASED_FILE_SYSTEM + /* You lose if separators don't match exactly in the + prefix. */ + && !strncasecmp (source_prefix, src->coverage.name, source_length) +#else + && !strncmp (source_prefix, src->coverage.name, source_length) +#endif + && IS_DIR_SEPARATOR (src->coverage.name[source_length])) + src->coverage.name += source_length + 1; + if (!stat (src->name, &status)) + src->file_time = status.st_mtime; + } + else + idx = name_map->src; + + if (name_search (file_name, name_map)) + { + /* Append the non-canonical name. */ + name_map = &names[n_names++]; + name_map->name = xstrdup (file_name); + name_map->src = idx; + } + + /* Resort the name map. */ + qsort (names, n_names, sizeof (*names), name_sort); + + check_date: + if (sources[idx].file_time > bbg_file_time) + { + static int info_emitted; + + fnotice (stderr, "%s:source file is newer than notes file '%s'\n", + file_name, bbg_file_name); + if (!info_emitted) + { + fnotice (stderr, + "(the message is only displayed one per source file)\n"); + info_emitted = 1; + } + sources[idx].file_time = 0; + } + + return idx; +} + +/* Read the notes file. Return list of functions read -- in reverse order. */ + +static function_t * +read_graph_file (void) +{ + unsigned version; + unsigned current_tag = 0; + function_t *fn = NULL; + function_t *fns = NULL; + function_t **fns_end = &fns; + unsigned src_idx = 0; + unsigned ix; + unsigned tag; + + if (!gcov_open (bbg_file_name, 1)) + { + fnotice (stderr, "%s:cannot open notes file\n", bbg_file_name); + return fns; + } + bbg_file_time = gcov_time (); + if (!gcov_magic (gcov_read_unsigned (), GCOV_NOTE_MAGIC)) + { + fnotice (stderr, "%s:not a gcov notes file\n", bbg_file_name); + gcov_close (); + return fns; + } + + 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, lineno; + 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 ()); + src_idx = find_source (gcov_read_string ()); + lineno = gcov_read_unsigned (); + + fn = XCNEW (function_t); + fn->name = function_name; + if (flag_demangled_names) + { + fn->demangled_name = cplus_demangle (fn->name, DMGL_PARAMS); + if (!fn->demangled_name) + fn->demangled_name = fn->name; + } + fn->ident = ident; + fn->lineno_checksum = lineno_checksum; + fn->cfg_checksum = cfg_checksum; + fn->src = src_idx; + fn->line = lineno; + + fn->line_next = NULL; + fn->next = NULL; + *fns_end = fn; + fns_end = &fn->next; + current_tag = tag; + } + 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 = XCNEWVEC (block_t, fn->num_blocks); + 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); + block_t *src_blk = &fn->blocks[src]; + unsigned mark_catches = 0; + struct arc_info *arc; + + if (src >= fn->num_blocks || fn->blocks[src].succ) + goto corrupt; + + while (num_dests--) + { + unsigned dest = gcov_read_unsigned (); + unsigned flags = gcov_read_unsigned (); + + if (dest >= fn->num_blocks) + goto corrupt; + arc = XCNEW (arc_t); + + arc->dst = &fn->blocks[dest]; + arc->src = src_blk; + + 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 = src_blk->succ; + src_blk->succ = arc; + src_blk->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; + mark_catches = 1; + } + else + { + /* Non-local return from a callee of this + function. The destination block is a setjmp. */ + arc->is_nonlocal_return = 1; + fn->blocks[dest].is_nonlocal_return = 1; + } + } + + if (!arc->on_tree) + fn->num_counts++; + } + + if (mark_catches) + { + /* We have a fake exit from this block. The other + non-fall through exits must be to catch handlers. + Mark them as catch arcs. */ + + for (arc = src_blk->succ; arc; arc = arc->succ_next) + if (!arc->fake && !arc->fall_through) + { + arc->is_throw = 1; + fn->has_catch = 1; + } + } + } + else if (fn && tag == GCOV_TAG_LINES) + { + unsigned blockno = gcov_read_unsigned (); + unsigned *line_nos = XCNEWVEC (unsigned, length - 1); + + 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_idx; + } + line_nos[ix++] = lineno; + } + else + { + const char *file_name = gcov_read_string (); + + if (!file_name) + break; + src_idx = find_source (file_name); + line_nos[ix++] = 0; + line_nos[ix++] = src_idx; + } + } + + 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); + break; + } + } + gcov_close (); + + if (!fns) + fnotice (stderr, "%s:no functions found\n", bbg_file_name); + + return fns; +} + +/* Reads profiles from the count file and attach to each + function. Return nonzero if fatal error. */ + +static int +read_count_file (function_t *fns) +{ + 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, assuming not executed\n", + da_file_name); + no_data_file = 1; + return 0; + } + 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 notes 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_PROGRAM_SUMMARY) + { + struct gcov_summary summary; + gcov_read_summary (&summary); + object_runs += summary.ctrs[GCOV_COUNTER_ARCS].runs; + program_count++; + } + else if (tag == GCOV_TAG_FUNCTION && !length) + ; /* placeholder */ + else if (tag == GCOV_TAG_FUNCTION && length == GCOV_TAG_FUNCTION_LENGTH) + { + unsigned ident; + struct function_info *fn_n; + + /* Try to find the function in the list. To speed up the + search, first start from the last function found. */ + ident = gcov_read_unsigned (); + fn_n = fns; + 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->lineno_checksum + || gcov_read_unsigned () != fn->cfg_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 = XCNEWVEC (gcov_type, fn->num_counts); + + 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. */ + + /* The arcs were built in reverse order. Fix that now. */ + for (ix = fn->num_blocks; ix--;) + { + arc_t *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; + } + + 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[ENTRY_BLOCK].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[ENTRY_BLOCK].num_pred = ~(unsigned)0; + + if (fn->blocks[EXIT_BLOCK].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[EXIT_BLOCK].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; + } +} + +/* Mark all the blocks only reachable via an incoming catch. */ + +static void +find_exception_blocks (function_t *fn) +{ + unsigned ix; + block_t **queue = XALLOCAVEC (block_t *, fn->num_blocks); + + /* First mark all blocks as exceptional. */ + for (ix = fn->num_blocks; ix--;) + fn->blocks[ix].exceptional = 1; + + /* Now mark all the blocks reachable via non-fake edges */ + queue[0] = fn->blocks; + queue[0]->exceptional = 0; + for (ix = 1; ix;) + { + block_t *block = queue[--ix]; + const arc_t *arc; + + for (arc = block->succ; arc; arc = arc->succ_next) + if (!arc->fake && !arc->is_throw && arc->dst->exceptional) + { + arc->dst->exceptional = 0; + queue[ix++] = arc->dst; + } + } +} + + +/* 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 GCOV_TYPE integer 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; +} + +/* Summary of execution */ + +static void +executed_summary (unsigned lines, unsigned executed) +{ + if (lines) + fnotice (stdout, "Lines executed:%s of %d\n", + format_gcov (executed, lines, 2), lines); + else + fnotice (stdout, "No executable lines\n"); +} + +/* Output summary info for a function or file. */ + +static void +function_summary (const coverage_t *coverage, const char *title) +{ + fnotice (stdout, "%s '%s'\n", title, coverage->name); + executed_summary (coverage->lines, coverage->lines_executed); + + 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"); + } +} + +/* Canonicalize the filename NAME by canonicalizing directory + separators, eliding . components and resolving .. components + appropriately. Always returns a unique string. */ + +static char * +canonicalize_name (const char *name) +{ + /* The canonical name cannot be longer than the incoming name. */ + char *result = XNEWVEC (char, strlen (name) + 1); + const char *base = name, *probe; + char *ptr = result; + char *dd_base; + int slash = 0; + +#if HAVE_DOS_BASED_FILE_SYSTEM + if (base[0] && base[1] == ':') + { + result[0] = base[0]; + result[1] = ':'; + base += 2; + ptr += 2; + } +#endif + for (dd_base = ptr; *base; base = probe) + { + size_t len; + + for (probe = base; *probe; probe++) + if (IS_DIR_SEPARATOR (*probe)) + break; + + len = probe - base; + if (len == 1 && base[0] == '.') + /* Elide a '.' directory */ + ; + else if (len == 2 && base[0] == '.' && base[1] == '.') + { + /* '..', we can only elide it and the previous directory, if + we're not a symlink. */ + struct stat ATTRIBUTE_UNUSED buf; + + *ptr = 0; + if (dd_base == ptr +#if defined (S_ISLNK) + /* S_ISLNK is not POSIX.1-1996. */ + || stat (result, &buf) || S_ISLNK (buf.st_mode) +#endif + ) + { + /* Cannot elide, or unreadable or a symlink. */ + dd_base = ptr + 2 + slash; + goto regular; + } + while (ptr != dd_base && *ptr != '/') + ptr--; + slash = ptr != result; + } + else + { + regular: + /* Regular pathname component. */ + if (slash) + *ptr++ = '/'; + memcpy (ptr, base, len); + ptr += len; + slash = 1; + } + + for (; IS_DIR_SEPARATOR (*probe); probe++) + continue; + } + *ptr = 0; + + return result; +} + +/* Generate an output file name. INPUT_NAME is the canonicalized main + input file and SRC_NAME is the canonicalized file name. + LONG_OUTPUT_NAMES and PRESERVE_PATHS affect name generation. 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 '##'. With preserve_paths we create a + filename from all path components of the source file, replacing '/' + with '#', and .. with '^', without it we simply take the basename + component. (Remember, the canonicalized name will already have + elided '.' components and converted \\ separators.) */ + +static char * +make_gcov_file_name (const char *input_name, const char *src_name) +{ + char *ptr; + char *result; + + if (flag_long_names && input_name && strcmp (src_name, input_name)) + { + /* Generate the input filename part. */ + result = XNEWVEC (char, strlen (input_name) + strlen (src_name) + 10); + + ptr = result; + ptr = mangle_name (input_name, ptr); + ptr[0] = ptr[1] = '#'; + ptr += 2; + } + else + { + result = XNEWVEC (char, strlen (src_name) + 10); + ptr = result; + } + + ptr = mangle_name (src_name, ptr); + strcpy (ptr, ".gcov"); + + return result; +} + +static char * +mangle_name (char const *base, char *ptr) +{ + size_t len; + + /* Generate the source filename part. */ + if (!flag_preserve_paths) + { + base = lbasename (base); + len = strlen (base); + memcpy (ptr, base, len); + ptr += len; + } + else + { + /* Convert '/' to '#', convert '..' to '^', + convert ':' to '~' on DOS based file system. */ + const char *probe; + +#if HAVE_DOS_BASED_FILE_SYSTEM + if (base[0] && base[1] == ':') + { + ptr[0] = base[0]; + ptr[1] = '~'; + ptr += 2; + base += 2; + } +#endif + for (; *base; base = probe) + { + size_t len; + + for (probe = base; *probe; probe++) + if (*probe == '/') + break; + len = probe - base; + if (len == 2 && base[0] == '.' && base[1] == '.') + *ptr++ = '^'; + else + { + memcpy (ptr, base, len); + ptr += len; + } + if (*probe) + { + *ptr++ = '#'; + probe++; + } + } + } + + return ptr; +} + +/* 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) + { + src = &sources[*++encoding]; + jx++; + } + else + { + line = &src->lines[*encoding]; + + if (coverage) + { + if (!line->exists) + coverage->lines++; + if (!line->count && block->count) + coverage->lines_executed++; + } + line->exists = 1; + if (!block->exceptional) + line->unexceptional = 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; + + if (!block_line) + block_line = &sources[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)" + : arc->is_throw ? " (throw)" : ""); + 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; + +} + +static const char * +read_line (FILE *file) +{ + static char *string; + static size_t string_len; + size_t pos = 0; + char *ptr; + + if (!string_len) + { + string_len = 200; + string = XNEWVEC (char, string_len); + } + + while ((ptr = fgets (string + pos, string_len - pos, file))) + { + size_t len = strlen (string + pos); + + if (string[pos + len - 1] == '\n') + { + string[pos + len - 1] = 0; + return string; + } + pos += len; + string = XRESIZEVEC (char, string, string_len * 2); + string_len *= 2; + } + + return pos ? string : NULL; +} + +/* 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. */ + const char *retval = ""; /* status of source file reading. */ + function_t *fn = NULL; + + fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, src->coverage.name); + if (!multiple_files) + { + fprintf (gcov_file, "%9s:%5d:Graph:%s\n", "-", 0, bbg_file_name); + fprintf (gcov_file, "%9s:%5d:Data:%s\n", "-", 0, + no_data_file ? "-" : da_file_name); + fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0, object_runs); + } + fprintf (gcov_file, "%9s:%5d:Programs:%u\n", "-", 0, program_count); + + source_file = fopen (src->name, "r"); + if (!source_file) + { + fnotice (stderr, "Cannot open source file %s\n", src->name); + retval = NULL; + } + else if (src->file_time == 0) + 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[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->num_blocks - 2, 0)); + fprintf (gcov_file, "\n"); + } + + if (retval) + retval = read_line (source_file); + + /* 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. */ + fprintf (gcov_file, "%9s:%5u:%s\n", + !line->exists ? "-" : line->count + ? format_gcov (line->count, 0, -1) + : line->unexceptional ? "#####" : "=====", line_num, + retval ? retval : "/*EOF*/"); + + 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) + : block->exceptional ? "%%%%%" : "$$$$$", + 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 = read_line (source_file)); line_num++) + fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval); + } + + if (source_file) + fclose (source_file); +} |