X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=gcc%2Fgcov.c;h=b8ce1ee0e096eb90d1a43dc0a5d5bf24c8be86f3;hb=c2b7062d584397685e0da236f2c0e430b818cbb1;hp=48bcdc0d4c39ef2dcc027451f167631a393c6e40;hpb=29a4ef18ec053a15ccc224d4cc5c8860b03496b4;p=gcc.git diff --git a/gcc/gcov.c b/gcc/gcov.c index 48bcdc0d4c3..b8ce1ee0e09 100644 --- a/gcc/gcov.c +++ b/gcc/gcov.c @@ -1,6 +1,6 @@ /* Gcov.c: prepend line execution counts and branch probabilities to a source file. - Copyright (C) 1990-2017 Free Software Foundation, Inc. + Copyright (C) 1990-2019 Free Software Foundation, Inc. Contributed by James E. Wilson of Cygnus Support. Mangled by Bob Manson of Cygnus Support. Mangled further by Nathan Sidwell @@ -34,6 +34,8 @@ along with Gcov; see the file COPYING3. If not see #define INCLUDE_ALGORITHM #define INCLUDE_VECTOR #define INCLUDE_STRING +#define INCLUDE_MAP +#define INCLUDE_SET #include "system.h" #include "coretypes.h" #include "tm.h" @@ -42,7 +44,10 @@ along with Gcov; see the file COPYING3. If not see #include "version.h" #include "demangle.h" #include "color-macros.h" +#include "pretty-print.h" +#include "json.h" +#include #include #include "md5.h" @@ -77,7 +82,7 @@ struct source_info; /* Describes an arc between two basic blocks. */ -typedef struct arc_info +struct arc_info { /* source and destination blocks. */ struct block_info *src; @@ -111,7 +116,7 @@ typedef struct arc_info /* Links to next arc on src and dst lists. */ struct arc_info *succ_next; struct arc_info *pred_next; -} arc_t; +}; /* Describes which locations (lines and files) are associated with a basic block. */ @@ -129,14 +134,14 @@ struct block_location_info /* Describes a basic block. Contains lists of arcs to successor and predecessor blocks. */ -typedef struct block_info +struct block_info { /* Constructor. */ block_info (); /* Chain of exit and entry arcs. */ - arc_t *succ; - arc_t *pred; + arc_info *succ; + arc_info *pred; /* Number of unprocessed exit and entry arcs. */ gcov_type num_succ; @@ -164,7 +169,7 @@ typedef struct block_info { /* Single line graph cycle workspace. Used for all-blocks mode. */ - arc_t *arc; + arc_info *arc; unsigned ident; } cycle; /* Used in all-blocks mode, after blocks are linked onto lines. */ @@ -173,7 +178,7 @@ typedef struct block_info line. */ struct block_info *chain; -} block_t; +}; block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0), id (0), count (0), count_valid (0), valid_chain (0), invalid_chain (0), @@ -183,16 +188,68 @@ block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0), cycle.arc = NULL; } +/* Describes a single line of source. Contains a chain of basic blocks + with code on it. */ + +struct line_info +{ + /* Default constructor. */ + line_info (); + + /* Return true when NEEDLE is one of basic blocks the line belongs to. */ + bool has_block (block_info *needle); + + /* Execution count. */ + gcov_type count; + + /* Branches from blocks that end on this line. */ + vector branches; + + /* blocks which start on this line. Used in all-blocks mode. */ + vector blocks; + + unsigned exists : 1; + unsigned unexceptional : 1; + unsigned has_unexecuted_block : 1; +}; + +line_info::line_info (): count (0), branches (), blocks (), exists (false), + unexceptional (0), has_unexecuted_block (0) +{ +} + +bool +line_info::has_block (block_info *needle) +{ + return std::find (blocks.begin (), blocks.end (), needle) != blocks.end (); +} + +/* Output demangled function names. */ + +static int flag_demangled_names = 0; + /* Describes a single function. Contains an array of basic blocks. */ -typedef struct function_info +struct function_info { function_info (); ~function_info (); + /* Return true when line N belongs to the function in source file SRC_IDX. + The line must be defined in body of the function, can't be inlined. */ + bool group_line_p (unsigned n, unsigned src_idx); + + /* Function filter based on function_info::artificial variable. */ + + static inline bool + is_artificial (function_info *fn) + { + return fn->artificial; + } + /* Name of function. */ - char *name; - char *demangled_name; + char *m_name; + char *m_demangled_name; unsigned ident; unsigned lineno_checksum; unsigned cfg_checksum; @@ -200,31 +257,85 @@ typedef struct function_info /* The graph contains at least one fake incoming edge. */ unsigned has_catch : 1; + /* True when the function is artificial and does not exist + in a source file. */ + unsigned artificial : 1; + + /* True when multiple functions start at a line in a source file. */ + unsigned is_group : 1; + /* Array of basic blocks. Like in GCC, the entry block is at blocks[0] and the exit block is at blocks[1]. */ #define ENTRY_BLOCK (0) #define EXIT_BLOCK (1) - vector blocks; + vector blocks; unsigned blocks_executed; /* Raw arc coverage counts. */ - gcov_type *counts; - unsigned num_counts; + vector counts; - /* First line number & file. */ - unsigned line; + /* First line number. */ + unsigned start_line; + + /* First line column. */ + unsigned start_column; + + /* Last line number. */ + unsigned end_line; + + /* Index of source file where the function is defined. */ unsigned src; - /* Next function in same source file. */ - struct function_info *next_file_fn; + /* Vector of line information. */ + vector lines; /* Next function. */ struct function_info *next; -} function_t; + + /* Get demangled name of a function. The demangled name + is converted when it is used for the first time. */ + char *get_demangled_name () + { + if (m_demangled_name == NULL) + { + m_demangled_name = cplus_demangle (m_name, DMGL_PARAMS); + if (!m_demangled_name) + m_demangled_name = m_name; + } + + return m_demangled_name; + } + + /* Get name of the function based on flag_demangled_names. */ + char *get_name () + { + return flag_demangled_names ? get_demangled_name () : m_name; + } + + /* Return number of basic blocks (without entry and exit block). */ + unsigned get_block_count () + { + return blocks.size () - 2; + } +}; + +/* Function info comparer that will sort functions according to starting + line. */ + +struct function_line_start_cmp +{ + inline bool operator() (const function_info *lhs, + const function_info *rhs) + { + return (lhs->start_line == rhs->start_line + ? lhs->start_column < rhs->start_column + : lhs->start_line < rhs->start_line); + } +}; /* Describes coverage of a file or function. */ -typedef struct coverage_info +struct coverage_info { int lines; int lines_executed; @@ -237,44 +348,8 @@ typedef struct coverage_info int calls_executed; char *name; -} coverage_t; - -/* Describes a single line of source. Contains a chain of basic blocks - with code on it. */ - -struct line_info -{ - /* Default constructor. */ - line_info (); - - /* Return true when NEEDLE is one of basic blocks the line belongs to. */ - bool has_block (block_t *needle); - - /* Execution count. */ - gcov_type count; - - /* Branches from blocks that end on this line. */ - vector branches; - - /* blocks which start on this line. Used in all-blocks mode. */ - vector blocks; - - unsigned exists : 1; - unsigned unexceptional : 1; - unsigned has_unexecuted_block : 1; }; -line_info::line_info (): count (0), branches (), blocks (), exists (false), - unexceptional (0), has_unexecuted_block (0) -{ -} - -bool -line_info::has_block (block_t *needle) -{ - return std::find (blocks.begin (), blocks.end (), needle) != blocks.end (); -} - /* Describes a file mentioned in the block graph. Contains an array of line info. */ @@ -283,6 +358,14 @@ struct source_info /* Default constructor. */ source_info (); + vector *get_functions_at_location (unsigned line_num) const; + + /* Register a new function. */ + void add_function (function_info *fn); + + /* Index of the source_info in sources vector. */ + unsigned index; + /* Canonical name of source file. */ char *name; time_t file_time; @@ -290,18 +373,53 @@ struct source_info /* Vector of line information. */ vector lines; - coverage_t coverage; + coverage_info coverage; + + /* Maximum line count in the source file. */ + unsigned int maximum_count; /* Functions in this source file. These are in ascending line number order. */ - function_t *functions; + vector functions; + + /* Line number to functions map. */ + vector *> line_to_function_map; }; -source_info::source_info (): name (NULL), file_time (), lines (), - coverage (), functions (NULL) +source_info::source_info (): index (0), name (NULL), file_time (), + lines (), coverage (), maximum_count (0), functions () { } +/* Register a new function. */ +void +source_info::add_function (function_info *fn) +{ + functions.push_back (fn); + + if (fn->start_line >= line_to_function_map.size ()) + line_to_function_map.resize (fn->start_line + 1); + + vector **slot = &line_to_function_map[fn->start_line]; + if (*slot == NULL) + *slot = new vector (); + + (*slot)->push_back (fn); +} + +vector * +source_info::get_functions_at_location (unsigned line_num) const +{ + if (line_num >= line_to_function_map.size ()) + return NULL; + + vector *slot = line_to_function_map[line_num]; + if (slot != NULL) + std::sort (slot->begin (), slot->end (), function_line_start_cmp ()); + + return slot; +} + class name_map { public: @@ -335,10 +453,11 @@ public: unsigned src; /* Source file */ }; -/* Holds a list of function basic block graphs. */ +/* Vector of all functions. */ +static vector functions; -static function_t *functions; -static function_t **fn_end = &functions; +/* Function ident to function_info * map. */ +static map ident_to_fn; /* Vector of source files. */ static vector sources; @@ -346,10 +465,13 @@ static vector sources; /* Mapping of file names to sources */ static vector names; +/* Record all processed files in order to warn about + a file being read multiple times. */ +static vector processed_files; + /* This holds data summary information. */ static unsigned object_runs; -static unsigned program_count; static unsigned total_lines; static unsigned total_executed; @@ -367,6 +489,12 @@ static char *bbg_file_name; /* Stamp of the bbg file */ static unsigned bbg_stamp; +/* Supports has_unexecuted_blocks functionality. */ +static unsigned bbg_supports_has_unexecuted_blocks; + +/* Working directory in which a TU was compiled. */ +static const char *bbg_cwd; + /* Name and file pointer of the input file for the count data (gcda). */ static char *da_file_name; @@ -394,18 +522,18 @@ static int flag_unconditional = 0; static int flag_gcov_file = 1; +/* Output to stdout instead to a gcov file. */ + +static int flag_use_stdout = 0; + /* 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'. */ +/* Output *.gcov file in JSON intermediate format used by consumers. */ -static int flag_intermediate_format = 0; - -/* Output demangled function names. */ - -static int flag_demangled_names = 0; +static int flag_json_format = 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, @@ -426,6 +554,10 @@ static int flag_verbose = 0; static int flag_use_colors = 0; +/* Use perf-like colors to indicate hot lines. */ + +static int flag_use_hotness_colors = 0; + /* Output count information for every basic block, not merely those that contain line number information. */ @@ -471,32 +603,34 @@ 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 process_all_functions (void); static void generate_results (const char *); static void create_file_names (const char *); 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 read_graph_file (void); +static int read_count_file (void); +static void solve_flow_graph (function_info *); +static void find_exception_blocks (function_info *); +static void add_branch_counts (coverage_info *, const arc_info *); +static void add_line_counts (coverage_info *, function_info *); static void executed_summary (unsigned, unsigned); -static void function_summary (const coverage_t *, const char *); +static void function_summary (const coverage_info *, const char *); static const char *format_gcov (gcov_type, gcov_type, int); static void accumulate_line_counts (source_info *); static void output_gcov_file (const char *, source_info *); -static int output_branch_count (FILE *, int, const arc_t *); +static int output_branch_count (FILE *, int, const arc_info *); static void output_lines (FILE *, const source_info *); static char *make_gcov_file_name (const char *, const char *); static char *mangle_name (const char *, char *); static void release_structures (void); extern int main (int, char **); -function_info::function_info (): name (NULL), demangled_name (NULL), +function_info::function_info (): m_name (NULL), m_demangled_name (NULL), ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0), - blocks (), blocks_executed (0), counts (NULL), num_counts (0), - line (0), src (0), next_file_fn (NULL), next (NULL) + artificial (0), is_group (0), + blocks (), blocks_executed (0), counts (), + start_line (0), start_column (), end_line (0), src (0), lines (), next (NULL) { } @@ -504,7 +638,7 @@ function_info::~function_info () { for (int i = blocks.size () - 1; i >= 0; i--) { - arc_t *arc, *arc_n; + arc_info *arc, *arc_n; for (arc = blocks[i].succ; arc; arc = arc_n) { @@ -512,10 +646,14 @@ function_info::~function_info () free (arc); } } - free (counts); - if (flag_demangled_names && demangled_name != name) - free (demangled_name); - free (name); + if (m_demangled_name != m_name) + free (m_demangled_name); + free (m_name); +} + +bool function_info::group_line_p (unsigned n, unsigned src_idx) +{ + return is_group && src == src_idx && start_line <= n && n <= end_line; } /* Cycle detection! @@ -530,8 +668,8 @@ function_info::~function_info () simple paths)--the node is unblocked only when it participates in a cycle. */ -typedef vector arc_vector_t; -typedef vector block_vector_t; +typedef vector arc_vector_t; +typedef vector block_vector_t; /* Enum with types of loop in CFG. */ @@ -576,7 +714,7 @@ handle_cycle (const arc_vector_t &edges, int64_t &count) blocked by U in BLOCK_LISTS. */ static void -unblock (const block_t *u, block_vector_t &blocked, +unblock (const block_info *u, block_vector_t &blocked, vector &block_lists) { block_vector_t::iterator it = find (blocked.begin (), blocked.end (), u); @@ -601,7 +739,7 @@ unblock (const block_t *u, block_vector_t &blocked, Returns what type of loop it contains. */ static loop_type -circuit (block_t *v, arc_vector_t &path, block_t *start, +circuit (block_info *v, arc_vector_t &path, block_info *start, block_vector_t &blocked, vector &block_lists, line_info &linfo, int64_t &count) { @@ -612,9 +750,9 @@ circuit (block_t *v, arc_vector_t &path, block_t *start, blocked.push_back (v); block_lists.push_back (block_vector_t ()); - for (arc_t *arc = v->succ; arc; arc = arc->succ_next) + for (arc_info *arc = v->succ; arc; arc = arc->succ_next) { - block_t *w = arc->dst; + block_info *w = arc->dst; if (w < start || !linfo.has_block (w)) continue; @@ -631,9 +769,9 @@ circuit (block_t *v, arc_vector_t &path, block_t *start, if (result != NO_LOOP) unblock (v, blocked, block_lists); else - for (arc_t *arc = v->succ; arc; arc = arc->succ_next) + for (arc_info *arc = v->succ; arc; arc = arc->succ_next) { - block_t *w = arc->dst; + block_info *w = arc->dst; if (w < start || !linfo.has_block (w)) continue; @@ -662,7 +800,7 @@ get_cycles_count (line_info &linfo, bool handle_negative_cycles = true) loop_type result = NO_LOOP; gcov_type count = 0; - for (vector::iterator it = linfo.blocks.begin (); + for (vector::iterator it = linfo.blocks.begin (); it != linfo.blocks.end (); it++) { arc_vector_t path; @@ -718,11 +856,14 @@ main (int argc, char **argv) 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 (); + if (flag_json_format || argno == argc - 1) + { + process_all_functions (); + generate_results (argv[argno]); + release_structures (); + } + } return 0; } @@ -745,7 +886,7 @@ print_usage (int error_p) fnotice (file, " -d, --display-progress Display progress information\n"); fnotice (file, " -f, --function-summaries Output summaries for each function\n"); fnotice (file, " -h, --help Print this help, then exit\n"); - fnotice (file, " -i, --intermediate-format Output .gcov file in intermediate text format\n"); + fnotice (file, " -i, --json-format Output JSON intermediate format into .gcov.json.gz file\n"); fnotice (file, " -j, --human-readable Output human readable numbers\n"); fnotice (file, " -k, --use-colors Emit colored output\n"); fnotice (file, " -l, --long-file-names Use long output file names for included\n\ @@ -754,8 +895,10 @@ print_usage (int error_p) 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, " -q, --use-hotness-colors Emit perf-like colored output for hot lines\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, " -t, --stdout Output to stdout instead of a file\n"); fnotice (file, " -u, --unconditional-branches Show unconditional branch counts too\n"); fnotice (file, " -v, --version Print version number, then exit\n"); fnotice (file, " -w, --verbose Print verbose informations\n"); @@ -771,7 +914,7 @@ static void print_version (void) { fnotice (stdout, "gcov %s%s\n", pkgversion_string, version_string); - fprintf (stdout, "Copyright %s 2017 Free Software Foundation, Inc.\n", + fprintf (stdout, "Copyright %s 2019 Free Software Foundation, Inc.\n", _("(C)")); fnotice (stdout, _("This is free software; see the source for copying conditions.\n" @@ -788,7 +931,7 @@ static const struct option options[] = { "all-blocks", no_argument, NULL, 'a' }, { "branch-probabilities", no_argument, NULL, 'b' }, { "branch-counts", no_argument, NULL, 'c' }, - { "intermediate-format", no_argument, NULL, 'i' }, + { "json-format", no_argument, NULL, 'i' }, { "human-readable", no_argument, NULL, 'j' }, { "no-output", no_argument, NULL, 'n' }, { "long-file-names", no_argument, NULL, 'l' }, @@ -799,10 +942,12 @@ static const struct option options[] = { "object-directory", required_argument, NULL, 'o' }, { "object-file", required_argument, NULL, 'o' }, { "source-prefix", required_argument, NULL, 's' }, + { "stdout", no_argument, NULL, 't' }, { "unconditional-branches", no_argument, NULL, 'u' }, { "display-progress", no_argument, NULL, 'd' }, { "hash-filenames", no_argument, NULL, 'x' }, { "use-colors", no_argument, NULL, 'k' }, + { "use-hotness-colors", no_argument, NULL, 'q' }, { 0, 0, 0, 0 } }; @@ -813,7 +958,7 @@ process_args (int argc, char **argv) { int opt; - const char *opts = "abcdfhijklmno:prs:uvwx"; + const char *opts = "abcdfhijklmno:pqrs:tuvwx"; while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1) { switch (opt) @@ -842,6 +987,9 @@ process_args (int argc, char **argv) case 'k': flag_use_colors = 1; break; + case 'q': + flag_use_hotness_colors = 1; + break; case 'm': flag_demangled_names = 1; break; @@ -865,18 +1013,21 @@ process_args (int argc, char **argv) flag_unconditional = 1; break; case 'i': - flag_intermediate_format = 1; - flag_gcov_file = 1; - break; - case 'd': - flag_display_progress = 1; - break; + flag_json_format = 1; + flag_gcov_file = 1; + break; + case 'd': + flag_display_progress = 1; + break; case 'x': flag_hash_filenames = 1; break; case 'w': flag_verbose = 1; break; + case 't': + flag_use_stdout = 1; + break; case 'v': print_version (); /* print_version will exit. */ @@ -889,132 +1040,295 @@ process_args (int argc, char **argv) return optind; } -/* Output the result in intermediate format used by 'lcov'. +/* Output intermediate LINE sitting on LINE_NUM to JSON OBJECT. */ -The intermediate format contains a single file named 'foo.cc.gcov', -with no source code included. +static void +output_intermediate_json_line (json::array *object, + line_info *line, unsigned line_num) +{ + if (!line->exists) + return; + + json::object *lineo = new json::object (); + lineo->set ("line_number", new json::number (line_num)); + lineo->set ("count", new json::number (line->count)); + lineo->set ("unexecuted_block", + new json::literal (line->has_unexecuted_block)); + + json::array *branches = new json::array (); + lineo->set ("branches", branches); + + vector::const_iterator it; + if (flag_branches) + for (it = line->branches.begin (); it != line->branches.end (); + it++) + { + if (!(*it)->is_unconditional && !(*it)->is_call_non_return) + { + json::object *branch = new json::object (); + branch->set ("count", new json::number ((*it)->count)); + branch->set ("throw", new json::literal ((*it)->is_throw)); + branch->set ("fallthrough", + new json::literal ((*it)->fall_through)); + branches->append (branch); + } + } + + object->append (lineo); +} -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. */ +/* 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.json.gz"; + 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 JSON intermediate format. + Source info SRC is dumped into JSON_FILES which is JSON array. */ static void -output_intermediate_file (FILE *gcov_file, source_info *src) +output_json_intermediate_file (json::array *json_files, source_info *src) { - unsigned line_num; /* current line number. */ - const line_info *line; /* current line info ptr. */ - function_t *fn; /* current function info ptr. */ + json::object *root = new json::object (); + json_files->append (root); + + root->set ("file", new json::string (src->name)); - fprintf (gcov_file, "file:%s\n", src->name); /* source file name */ + json::array *functions = new json::array (); + root->set ("functions", functions); - for (fn = src->functions; fn; fn = fn->next_file_fn) + std::sort (src->functions.begin (), src->functions.end (), + function_line_start_cmp ()); + for (vector::iterator it = src->functions.begin (); + it != src->functions.end (); it++) { - /* function:,, */ - fprintf (gcov_file, "function:%d,%s,%s\n", fn->line, - format_gcov (fn->blocks[0].count, 0, -1), - flag_demangled_names ? fn->demangled_name : fn->name); + json::object *function = new json::object (); + function->set ("name", new json::string ((*it)->m_name)); + function->set ("demangled_name", + new json::string ((*it)->get_demangled_name ())); + function->set ("start_line", new json::number ((*it)->start_line)); + function->set ("end_line", new json::number ((*it)->end_line)); + function->set ("blocks", + new json::number ((*it)->get_block_count ())); + function->set ("blocks_executed", + new json::number ((*it)->blocks_executed)); + function->set ("execution_count", + new json::number ((*it)->blocks[0].count)); + + functions->append (function); } - for (line_num = 1, line = &src->lines[line_num]; - line_num < src->lines.size (); - line_num++, line++) + json::array *lineso = new json::array (); + root->set ("lines", lineso); + + for (unsigned line_num = 1; line_num <= src->lines.size (); line_num++) { - if (line->exists) - fprintf (gcov_file, "lcount:%u,%s,%d\n", line_num, - format_gcov (line->count, 0, -1), line->has_unexecuted_block); - if (flag_branches) - for (vector::const_iterator it = line->branches.begin (); - it != line->branches.end (); it++) + vector *fns = src->get_functions_at_location (line_num); + + if (fns != NULL) + /* Print first group functions that begin on the line. */ + for (vector::iterator it2 = fns->begin (); + it2 != fns->end (); it2++) { - if (!(*it)->is_unconditional && !(*it)->is_call_non_return) + vector &lines = (*it2)->lines; + for (unsigned i = 0; i < lines.size (); i++) { - const char *branch_type; - /* branch:, - branch_coverage_type - : notexec (Branch not executed) - : taken (Branch executed and taken) - : nottaken (Branch executed, but not taken) - */ - if ((*it)->src->count) - branch_type = ((*it)->count > 0) ? "taken" : "nottaken"; - else - branch_type = "notexec"; - fprintf (gcov_file, "branch:%d,%s\n", line_num, branch_type); + line_info *line = &lines[i]; + output_intermediate_json_line (lineso, line, line_num + i); } } + + /* Follow with lines associated with the source file. */ + if (line_num < src->lines.size ()) + output_intermediate_json_line (lineso, &src->lines[line_num], line_num); } } +/* Function start pair. */ +struct function_start +{ + unsigned source_file_idx; + unsigned start_line; +}; + +/* Traits class for function start hash maps below. */ + +struct function_start_pair_hash : typed_noop_remove +{ + typedef function_start value_type; + typedef function_start compare_type; + + static hashval_t + hash (const function_start &ref) + { + inchash::hash hstate (0); + hstate.add_int (ref.source_file_idx); + hstate.add_int (ref.start_line); + return hstate.end (); + } + + static bool + equal (const function_start &ref1, const function_start &ref2) + { + return (ref1.source_file_idx == ref2.source_file_idx + && ref1.start_line == ref2.start_line); + } + + static void + mark_deleted (function_start &ref) + { + ref.start_line = ~1U; + } + + static void + mark_empty (function_start &ref) + { + ref.start_line = ~2U; + } + + static bool + is_deleted (const function_start &ref) + { + return ref.start_line == ~1U; + } + + static bool + is_empty (const function_start &ref) + { + return ref.start_line == ~2U; + } +}; + /* Process a single input file. */ static void 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) + for (unsigned i = 0; i < processed_files.size (); i++) + if (strcmp (da_file_name, processed_files[i]) == 0) + { + fnotice (stderr, "'%s' file is already processed\n", + file_name); + return; + } + + processed_files.push_back (xstrdup (da_file_name)); + + read_graph_file (); + read_count_file (); +} + +/* Process all functions in all files. */ + +static void +process_all_functions (void) +{ + hash_map fn_map; + + /* Identify group functions. */ + for (vector::iterator it = functions.begin (); + it != functions.end (); it++) + if (!(*it)->artificial) + { + function_start needle; + needle.source_file_idx = (*it)->src; + needle.start_line = (*it)->start_line; + + function_info **slot = fn_map.get (needle); + if (slot) + { + (*slot)->is_group = 1; + (*it)->is_group = 1; + } + else + fn_map.put (needle, *it); + } + + /* Remove all artificial function. */ + functions.erase (remove_if (functions.begin (), functions.end (), + function_info::is_artificial), functions.end ()); + + for (vector::iterator it = functions.begin (); + it != functions.end (); it++) { - function_t *fn = fns; + function_info *fn = *it; + unsigned src = fn->src; - fns = fn->next; - fn->next = NULL; - if (fn->counts || no_data_file) + if (!fn->counts.empty () || no_data_file) { - unsigned src = fn->src; - unsigned line = fn->line; - unsigned block_no; - function_t *probe, **prev; - - /* Now insert it into the source file's list of - functions. Normally functions will be encountered in - ascending order, so a simple scan is quick. Note we're - building this list in reverse order. */ - for (prev = &sources[src].functions; - (probe = *prev); prev = &probe->next_file_fn) - if (probe->line <= line) - break; - fn->next_file_fn = probe; - *prev = fn; + source_info *s = &sources[src]; + s->add_function (fn); /* Mark last line in files touched by function. */ - for (block_no = 0; block_no != fn->blocks.size (); block_no++) + for (unsigned block_no = 0; block_no != fn->blocks.size (); + block_no++) { - block_t *block = &fn->blocks[block_no]; + block_info *block = &fn->blocks[block_no]; for (unsigned i = 0; i < block->locations.size (); i++) { - unsigned s = block->locations[i].source_file_idx; - /* Sort lines of locations. */ sort (block->locations[i].lines.begin (), block->locations[i].lines.end ()); if (!block->locations[i].lines.empty ()) { + s = &sources[block->locations[i].source_file_idx]; unsigned last_line - = block->locations[i].lines.back () + 1; - if (last_line > sources[s].lines.size ()) - sources[s].lines.resize (last_line); + = block->locations[i].lines.back (); + + /* Record new lines for the function. */ + if (last_line >= s->lines.size ()) + { + s = &sources[block->locations[i].source_file_idx]; + unsigned last_line + = block->locations[i].lines.back (); + + /* Record new lines for the function. */ + if (last_line >= s->lines.size ()) + { + /* Record new lines for a source file. */ + s->lines.resize (last_line + 1); + } + } } } } + /* Allocate lines for group function, following start_line + and end_line information of the function. */ + if (fn->is_group) + fn->lines.resize (fn->end_line - fn->start_line + 1); + solve_flow_graph (fn); if (fn->has_catch) find_exception_blocks (fn); - *fn_end = fn; - fn_end = &fn->next; } else - /* The function was not in the executable -- some other - instance must have been selected. */ - delete fn; + { + /* The function was not in the executable -- some other + instance must have been selected. */ + } } } @@ -1027,19 +1341,16 @@ output_gcov_file (const char *file_name, source_info *src) { FILE *gcov_file = fopen (gcov_file_name, "w"); if (gcov_file) - { - fnotice (stdout, "Creating '%s'\n", gcov_file_name); - - if (flag_intermediate_format) - output_intermediate_file (gcov_file, src); - else - output_lines (gcov_file, src); - if (ferror (gcov_file)) - fnotice (stderr, "Error writing output file '%s'\n", gcov_file_name); - fclose (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); + fnotice (stderr, "Could not open output file '%s'\n", gcov_file_name); } else { @@ -1052,14 +1363,16 @@ output_gcov_file (const char *file_name, source_info *src) static void generate_results (const char *file_name) { - function_t *fn; + char *gcov_intermediate_filename; - for (fn = functions; fn; fn = fn->next) + for (vector::iterator it = functions.begin (); + it != functions.end (); it++) { - coverage_t coverage; + function_info *fn = *it; + coverage_info coverage; memset (&coverage, 0, sizeof (coverage)); - coverage.name = flag_demangled_names ? fn->demangled_name : fn->name; + coverage.name = fn->get_name (); add_line_counts (flag_function_summary ? &coverage : NULL, fn); if (flag_function_summary) { @@ -1081,6 +1394,18 @@ generate_results (const char *file_name) file_name = canonicalize_name (file_name); } + gcov_intermediate_filename = get_gcov_intermediate_filename (file_name); + + json::object *root = new json::object (); + root->set ("format_version", new json::string ("1")); + root->set ("gcc_version", new json::string (version_string)); + + if (bbg_cwd != NULL) + root->set ("current_working_directory", new json::string (bbg_cwd)); + + json::array *json_files = new json::array (); + root->set ("files", json_files); + for (vector::iterator it = sources.begin (); it != sources.end (); it++) { @@ -1100,14 +1425,60 @@ generate_results (const char *file_name) } accumulate_line_counts (src); - function_summary (&src->coverage, "File"); + + if (!flag_use_stdout) + function_summary (&src->coverage, "File"); total_lines += src->coverage.lines; total_executed += src->coverage.lines_executed; if (flag_gcov_file) { - output_gcov_file (file_name, src); - fnotice (stdout, "\n"); - } + if (flag_json_format) + output_json_intermediate_file (json_files, src); + else + { + if (flag_use_stdout) + { + if (src->coverage.lines) + output_lines (stdout, src); + } + else + { + output_gcov_file (file_name, src); + fnotice (stdout, "\n"); + } + } + } + } + + if (flag_gcov_file && flag_json_format) + { + if (flag_use_stdout) + { + root->dump (stdout); + printf ("\n"); + } + else + { + pretty_printer pp; + root->print (&pp); + pp_formatted_text (&pp); + + gzFile output = gzopen (gcov_intermediate_filename, "w"); + if (output == NULL) + { + fnotice (stderr, "Cannot open JSON output file %s\n", + gcov_intermediate_filename); + return; + } + + if (gzputs (output, pp_formatted_text (&pp)) == EOF + || gzclose (output)) + { + fnotice (stderr, "Error writing JSON output file %s\n", + gcov_intermediate_filename); + return; + } + } } if (!file_name) @@ -1119,13 +1490,14 @@ generate_results (const char *file_name) static void release_structures (void) { - function_t *fn; - - while ((fn = functions)) - { - functions = fn->next; - delete fn; - } + for (vector::iterator it = functions.begin (); + it != functions.end (); it++) + delete (*it); + + sources.resize (0); + names.resize (0); + functions.resize (0); + ident_to_fn.clear (); } /* Generate the names of the graph and data files. If OBJECT_DIRECTORY @@ -1237,6 +1609,7 @@ find_source (const char *file_name) src = &sources.back (); src->name = canon; src->coverage.name = src->name; + src->index = idx; if (source_length #if HAVE_DOS_BASED_FILE_SYSTEM /* You lose if separators don't match exactly in the @@ -1282,29 +1655,26 @@ find_source (const char *file_name) return idx; } -/* Read the notes file. Return list of functions read -- in reverse order. */ +/* Read the notes file. Save functions to FUNCTIONS global vector. */ -static function_t * +static void read_graph_file (void) { unsigned version; unsigned current_tag = 0; - function_t *fn = NULL; - function_t *fns = NULL; - function_t **fns_end = &fns; unsigned tag; if (!gcov_open (bbg_file_name, 1)) { fnotice (stderr, "%s:cannot open notes file\n", bbg_file_name); - return fns; + return; } 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; + return; } version = gcov_read_unsigned (); @@ -1319,7 +1689,10 @@ read_graph_file (void) bbg_file_name, v, e); } bbg_stamp = gcov_read_unsigned (); + bbg_cwd = xstrdup (gcov_read_string ()); + bbg_supports_has_unexecuted_blocks = gcov_read_unsigned (); + function_info *fn = NULL; while ((tag = gcov_read_unsigned ())) { unsigned length = gcov_read_unsigned (); @@ -1328,41 +1701,40 @@ read_graph_file (void) if (tag == GCOV_TAG_FUNCTION) { char *function_name; - unsigned ident, lineno; + unsigned ident; unsigned lineno_checksum, cfg_checksum; ident = gcov_read_unsigned (); lineno_checksum = gcov_read_unsigned (); cfg_checksum = gcov_read_unsigned (); function_name = xstrdup (gcov_read_string ()); + unsigned artificial = gcov_read_unsigned (); unsigned src_idx = find_source (gcov_read_string ()); - lineno = gcov_read_unsigned (); + unsigned start_line = gcov_read_unsigned (); + unsigned start_column = gcov_read_unsigned (); + unsigned end_line = gcov_read_unsigned (); - fn = new function_t; - fn->name = function_name; - if (flag_demangled_names) - { - fn->demangled_name = cplus_demangle (fn->name, DMGL_PARAMS); - if (!fn->demangled_name) - fn->demangled_name = fn->name; - } + fn = new function_info (); + functions.push_back (fn); + ident_to_fn[ident] = fn; + + fn->m_name = function_name; fn->ident = ident; fn->lineno_checksum = lineno_checksum; fn->cfg_checksum = cfg_checksum; fn->src = src_idx; - fn->line = lineno; + fn->start_line = start_line; + fn->start_column = start_column; + fn->end_line = end_line; + fn->artificial = artificial; - fn->next_file_fn = NULL; - fn->next = NULL; - *fns_end = fn; - fns_end = &fn->next; current_tag = tag; } else if (fn && tag == GCOV_TAG_BLOCKS) { if (!fn->blocks.empty ()) fnotice (stderr, "%s:already seen blocks for '%s'\n", - bbg_file_name, fn->name); + bbg_file_name, fn->get_name ()); else fn->blocks.resize (gcov_read_unsigned ()); } @@ -1371,7 +1743,7 @@ read_graph_file (void) unsigned src = gcov_read_unsigned (); fn->blocks[src].id = src; unsigned num_dests = GCOV_TAG_ARCS_NUM (length); - block_t *src_blk = &fn->blocks[src]; + block_info *src_blk = &fn->blocks[src]; unsigned mark_catches = 0; struct arc_info *arc; @@ -1385,7 +1757,7 @@ read_graph_file (void) if (dest >= fn->blocks.size ()) goto corrupt; - arc = XCNEW (arc_t); + arc = XCNEW (arc_info); arc->dst = &fn->blocks[dest]; arc->src = src_blk; @@ -1424,7 +1796,7 @@ read_graph_file (void) } if (!arc->on_tree) - fn->num_counts++; + fn->counts.push_back (0); } if (mark_catches) @@ -1444,7 +1816,7 @@ read_graph_file (void) else if (fn && tag == GCOV_TAG_LINES) { unsigned blockno = gcov_read_unsigned (); - block_t *block = &fn->blocks[blockno]; + block_info *block = &fn->blocks[blockno]; if (blockno >= fn->blocks.size ()) goto corrupt; @@ -1481,23 +1853,22 @@ read_graph_file (void) } gcov_close (); - if (!fns) + if (functions.empty ()) 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) +read_count_file (void) { unsigned ix; unsigned version; unsigned tag; - function_t *fn = NULL; + function_info *fn = NULL; int error = 0; + map::iterator it; if (!gcov_open (da_file_name, 1)) { @@ -1536,39 +1907,22 @@ read_count_file (function_t *fns) unsigned length = gcov_read_unsigned (); unsigned long base = gcov_position (); - if (tag == GCOV_TAG_PROGRAM_SUMMARY) + if (tag == GCOV_TAG_OBJECT_SUMMARY) { struct gcov_summary summary; gcov_read_summary (&summary); - object_runs += summary.ctrs[GCOV_COUNTER_ARCS].runs; - program_count++; + object_runs = summary.runs; } 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; - } + fn = NULL; + it = ident_to_fn.find (ident); + if (it != ident_to_fn.end ()) + fn = it->second; if (!fn) ; @@ -1577,19 +1931,16 @@ read_count_file (function_t *fns) { mismatch:; fnotice (stderr, "%s:profile mismatch for '%s'\n", - da_file_name, fn->name); + da_file_name, fn->get_name ()); goto cleanup; } } else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn) { - if (length != GCOV_TAG_COUNTER_LENGTH (fn->num_counts)) + if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ())) goto mismatch; - if (!fn->counts) - fn->counts = XCNEWVEC (gcov_type, fn->num_counts); - - for (ix = 0; ix != fn->num_counts; ix++) + for (ix = 0; ix != fn->counts.size (); ix++) fn->counts[ix] += gcov_read_counter (); } gcov_sync (base, length); @@ -1612,19 +1963,19 @@ read_count_file (function_t *fns) to the blocks and the uninstrumented arcs. */ static void -solve_flow_graph (function_t *fn) +solve_flow_graph (function_info *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. */ + arc_info *arc; + gcov_type *count_ptr = &fn->counts.front (); + block_info *blk; + block_info *valid_blocks = NULL; /* valid, but unpropagated blocks. */ + block_info *invalid_blocks = NULL; /* invalid, but inferable blocks. */ /* The arcs were built in reverse order. Fix that now. */ for (ix = fn->blocks.size (); ix--;) { - arc_t *arc_p, *arc_n; + arc_info *arc_p, *arc_n; for (arc_p = NULL, arc = fn->blocks[ix].succ; arc; arc_p = arc, arc = arc_n) @@ -1645,12 +1996,12 @@ solve_flow_graph (function_t *fn) if (fn->blocks.size () < 2) fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n", - bbg_file_name, fn->name); + bbg_file_name, fn->get_name ()); else { if (fn->blocks[ENTRY_BLOCK].num_pred) fnotice (stderr, "%s:'%s' has arcs to entry block\n", - bbg_file_name, fn->name); + bbg_file_name, fn->get_name ()); else /* We can't deduce the entry block counts from the lack of predecessors. */ @@ -1658,7 +2009,7 @@ solve_flow_graph (function_t *fn) if (fn->blocks[EXIT_BLOCK].num_succ) fnotice (stderr, "%s:'%s' has arcs from exit block\n", - bbg_file_name, fn->name); + bbg_file_name, fn->get_name ()); else /* Likewise, we can't deduce exit block counts from the lack of its successors. */ @@ -1670,7 +2021,7 @@ solve_flow_graph (function_t *fn) for (unsigned i = 0; i < fn->blocks.size (); i++) { blk = &fn->blocks[i]; - block_t const *prev_dst = NULL; + block_info const *prev_dst = NULL; int out_of_order = 0; int non_fake_succ = 0; @@ -1717,12 +2068,12 @@ solve_flow_graph (function_t *fn) smart sort. */ if (out_of_order) { - arc_t *start = blk->succ; + arc_info *start = blk->succ; unsigned changes = 1; while (changes) { - arc_t *arc, *arc_p, *arc_n; + arc_info *arc, *arc_p, *arc_n; changes = 0; for (arc_p = NULL, arc = start; (arc_n = arc->succ_next);) @@ -1760,7 +2111,7 @@ solve_flow_graph (function_t *fn) while ((blk = invalid_blocks)) { gcov_type total = 0; - const arc_t *arc; + const arc_info *arc; invalid_blocks = blk->chain; blk->invalid_chain = 0; @@ -1782,13 +2133,13 @@ solve_flow_graph (function_t *fn) while ((blk = valid_blocks)) { gcov_type total; - arc_t *arc, *inv_arc; + arc_info *arc, *inv_arc; valid_blocks = blk->chain; blk->valid_chain = 0; if (blk->num_succ == 1) { - block_t *dst; + block_info *dst; total = blk->count; inv_arc = NULL; @@ -1824,7 +2175,7 @@ solve_flow_graph (function_t *fn) } if (blk->num_pred == 1) { - block_t *src; + block_info *src; total = blk->count; inv_arc = NULL; @@ -1867,7 +2218,7 @@ solve_flow_graph (function_t *fn) if (!fn->blocks[i].count_valid) { fnotice (stderr, "%s:graph is unsolvable for '%s'\n", - bbg_file_name, fn->name); + bbg_file_name, fn->get_name ()); break; } } @@ -1875,10 +2226,10 @@ solve_flow_graph (function_t *fn) /* Mark all the blocks only reachable via an incoming catch. */ static void -find_exception_blocks (function_t *fn) +find_exception_blocks (function_info *fn) { unsigned ix; - block_t **queue = XALLOCAVEC (block_t *, fn->blocks.size ()); + block_info **queue = XALLOCAVEC (block_info *, fn->blocks.size ()); /* First mark all blocks as exceptional. */ for (ix = fn->blocks.size (); ix--;) @@ -1889,8 +2240,8 @@ find_exception_blocks (function_t *fn) queue[0]->exceptional = 0; for (ix = 1; ix;) { - block_t *block = queue[--ix]; - const arc_t *arc; + block_info *block = queue[--ix]; + const arc_info *arc; for (arc = block->succ; arc; arc = arc->succ_next) if (!arc->fake && !arc->is_throw && arc->dst->exceptional) @@ -1905,7 +2256,7 @@ find_exception_blocks (function_t *fn) /* Increment totals in COVERAGE according to arc ARC. */ static void -add_branch_counts (coverage_t *coverage, const arc_t *arc) +add_branch_counts (coverage_info *coverage, const arc_info *arc) { if (arc->is_call_non_return) { @@ -1945,56 +2296,30 @@ format_count (gcov_type count) if (count + divisor / 2 < 1000 * divisor) break; } - gcov_type r = (count + divisor / 2) / divisor; - sprintf (buffer, "%" PRId64 "%c", r, units[i]); + float r = 1.0f * count / divisor; + sprintf (buffer, "%.1f%c", r, units[i]); return buffer; } /* 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 + count. If DECIMAL_PLACES >= 0, format TOP/BOTTOM * 100 to DECIMAL_PLACES. + If DECIMAL_PLACES is zero, no decimal point is printed. Only print 100% when + TOP==BOTTOM and only print 0% when TOP=0. If DECIMAL_PLACES < 0, then simply format TOP. Return pointer to a static string. */ static char const * -format_gcov (gcov_type top, gcov_type bottom, int dp) +format_gcov (gcov_type top, gcov_type bottom, int decimal_places) { static char buffer[20]; - /* Handle invalid values that would result in a misleading value. */ - if (bottom != 0 && top > bottom && dp >= 0) + if (decimal_places >= 0) { - sprintf (buffer, "NAN %%"); - return buffer; - } + float ratio = bottom ? 100.0f * top / bottom: 0; - 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] = '.'; - } + /* Round up to 1% if there's a small non-zero value. */ + if (ratio > 0.0f && ratio < 0.5f && decimal_places == 0) + ratio = 1.0f; + sprintf (buffer, "%.*f%%", decimal_places, ratio); } else return format_count (top); @@ -2017,7 +2342,7 @@ executed_summary (unsigned lines, unsigned executed) /* Output summary info for a function or file. */ static void -function_summary (const coverage_t *coverage, const char *title) +function_summary (const coverage_info *coverage, const char *title) { fnotice (stdout, "%s '%s'\n", title, coverage->name); executed_summary (coverage->lines, coverage->lines_executed); @@ -2209,42 +2534,7 @@ mangle_name (char const *base, char *ptr) 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++; - } - } - } + ptr = mangle_path (base); return ptr; } @@ -2254,67 +2544,134 @@ mangle_name (char const *base, char *ptr) the appropriate basic block. */ static void -add_line_counts (coverage_t *coverage, function_t *fn) +add_line_counts (coverage_info *coverage, function_info *fn) { bool has_any_line = false; /* Scan each basic block. */ for (unsigned ix = 0; ix != fn->blocks.size (); ix++) { line_info *line = NULL; - block_t *block = &fn->blocks[ix]; + block_info *block = &fn->blocks[ix]; if (block->count && ix && ix + 1 != fn->blocks.size ()) fn->blocks_executed++; for (unsigned i = 0; i < block->locations.size (); i++) { - source_info *src = &sources[block->locations[i].source_file_idx]; - + unsigned src_idx = block->locations[i].source_file_idx; vector &lines = block->locations[i].lines; + + block->cycle.arc = NULL; + block->cycle.ident = ~0U; + for (unsigned j = 0; j < lines.size (); j++) { - line = &src->lines[lines[j]]; - if (coverage) + unsigned ln = lines[j]; + + /* Line belongs to a function that is in a group. */ + if (fn->group_line_p (ln, src_idx)) { - if (!line->exists) - coverage->lines++; - if (!line->count && block->count) - coverage->lines_executed++; + gcc_assert (lines[j] - fn->start_line < fn->lines.size ()); + line = &(fn->lines[lines[j] - fn->start_line]); + line->exists = 1; + if (!block->exceptional) + { + line->unexceptional = 1; + if (block->count == 0) + line->has_unexecuted_block = 1; + } + line->count += block->count; } - line->exists = 1; - if (!block->exceptional) + else { - line->unexceptional = 1; - if (block->count == 0) - line->has_unexecuted_block = 1; + gcc_assert (ln < sources[src_idx].lines.size ()); + line = &(sources[src_idx].lines[ln]); + if (coverage) + { + if (!line->exists) + coverage->lines++; + if (!line->count && block->count) + coverage->lines_executed++; + } + line->exists = 1; + if (!block->exceptional) + { + line->unexceptional = 1; + if (block->count == 0) + line->has_unexecuted_block = 1; + } + line->count += block->count; } - line->count += block->count; } - } - block->cycle.arc = NULL; - block->cycle.ident = ~0U; - has_any_line = true; - if (!ix || ix + 1 == fn->blocks.size ()) - /* Entry or exit block */; - else if (line != NULL) - { - line->blocks.push_back (block); + has_any_line = true; - if (flag_branches) + if (!ix || ix + 1 == fn->blocks.size ()) + /* Entry or exit block. */; + else if (line != NULL) { - arc_t *arc; + line->blocks.push_back (block); - for (arc = block->succ; arc; arc = arc->succ_next) + if (flag_branches) { - line->branches.push_back (arc); - if (coverage && !arc->is_unconditional) - add_branch_counts (coverage, arc); + arc_info *arc; + + for (arc = block->succ; arc; arc = arc->succ_next) + line->branches.push_back (arc); } } } } if (!has_any_line) - fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name); + fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, + fn->get_name ()); +} + +/* Accumulate info for LINE that belongs to SRC source file. If ADD_COVERAGE + is set to true, update source file summary. */ + +static void accumulate_line_info (line_info *line, source_info *src, + bool add_coverage) +{ + if (add_coverage) + for (vector::iterator it = line->branches.begin (); + it != line->branches.end (); it++) + add_branch_counts (&src->coverage, *it); + + if (!line->blocks.empty ()) + { + /* The user expects the line count to be the number of times + a line has been executed. Simply summing the block count + will give an artificially high number. The Right Thing + is to sum the entry counts to the graph of blocks on this + line, then find the elementary cycles of the local graph + and add the transition counts of those cycles. */ + gcov_type count = 0; + + /* Cycle detection. */ + for (vector::iterator it = line->blocks.begin (); + it != line->blocks.end (); it++) + { + for (arc_info *arc = (*it)->pred; arc; arc = arc->pred_next) + if (!line->has_block (arc->src)) + count += arc->count; + for (arc_info *arc = (*it)->succ; arc; arc = arc->succ_next) + arc->cs_count = arc->count; + } + + /* Now, add the count of loops entirely on this line. */ + count += get_cycles_count (*line); + line->count = count; + + if (line->count > src->maximum_count) + src->maximum_count = line->count; + } + + if (line->exists && add_coverage) + { + src->coverage.lines++; + if (line->count) + src->coverage.lines_executed++; + } } /* Accumulate the line counts of a file. */ @@ -2322,74 +2679,70 @@ add_line_counts (coverage_t *coverage, function_t *fn) static void accumulate_line_counts (source_info *src) { - function_t *fn, *fn_p, *fn_n; - unsigned ix = 0; - - /* Reverse the function order. */ - for (fn = src->functions, fn_p = NULL; fn; fn_p = fn, fn = fn_n) + /* First work on group functions. */ + for (vector::iterator it = src->functions.begin (); + it != src->functions.end (); it++) { - fn_n = fn->next_file_fn; - fn->next_file_fn = fn_p; + function_info *fn = *it; + + if (fn->src != src->index || !fn->is_group) + continue; + + for (vector::iterator it2 = fn->lines.begin (); + it2 != fn->lines.end (); it2++) + { + line_info *line = &(*it2); + accumulate_line_info (line, src, false); + } } - src->functions = fn_p; - for (vector::reverse_iterator it = src->lines.rbegin (); - it != src->lines.rend (); it++) - { - line_info *line = &(*it); - if (!line->blocks.empty ()) - { - /* The user expects the line count to be the number of times - a line has been executed. Simply summing the block count - will give an artificially high number. The Right Thing - is to sum the entry counts to the graph of blocks on this - line, then find the elementary cycles of the local graph - and add the transition counts of those cycles. */ - gcov_type count = 0; - - /* Sum the entry arcs. */ - for (vector::iterator it = line->blocks.begin (); - it != line->blocks.end (); it++) - { - arc_t *arc; + /* Work on global lines that line in source file SRC. */ + for (vector::iterator it = src->lines.begin (); + it != src->lines.end (); it++) + accumulate_line_info (&(*it), src, true); - for (arc = (*it)->pred; arc; arc = arc->pred_next) - if (flag_branches) - add_branch_counts (&src->coverage, arc); - } + /* If not using intermediate mode, sum lines of group functions and + add them to lines that live in a source file. */ + if (!flag_json_format) + for (vector::iterator it = src->functions.begin (); + it != src->functions.end (); it++) + { + function_info *fn = *it; - /* Cycle detection. */ - for (vector::iterator it = line->blocks.begin (); - it != line->blocks.end (); it++) - { - for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next) - if (!line->has_block (arc->src)) - count += arc->count; - for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next) - arc->cs_count = arc->count; - } + if (fn->src != src->index || !fn->is_group) + continue; - /* Now, add the count of loops entirely on this line. */ - count += get_cycles_count (*line); - line->count = count; - } + for (unsigned i = 0; i < fn->lines.size (); i++) + { + line_info *fn_line = &fn->lines[i]; + if (fn_line->exists) + { + unsigned ln = fn->start_line + i; + line_info *src_line = &src->lines[ln]; - if (line->exists) - { - src->coverage.lines++; - if (line->count) - src->coverage.lines_executed++; - } + if (!src_line->exists) + src->coverage.lines++; + if (!src_line->count && fn_line->count) + src->coverage.lines_executed++; - ix++; - } + src_line->count += fn_line->count; + src_line->exists = 1; + + if (fn_line->has_unexecuted_block) + src_line->has_unexecuted_block = 1; + + if (fn_line->unexceptional) + src_line->unexceptional = 1; + } + } + } } /* Output information about ARC number IX. Returns nonzero if anything is output. */ static int -output_branch_count (FILE *gcov_file, int ix, const arc_t *arc) +output_branch_count (FILE *gcov_file, int ix, const arc_info *arc) { if (arc->is_call_non_return) { @@ -2487,7 +2840,8 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional, bool has_unexecuted_block, gcov_type count, unsigned line_num, const char *exceptional_string, - const char *unexceptional_string) + const char *unexceptional_string, + unsigned int maximum_count) { string s; if (exists) @@ -2495,12 +2849,14 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional, if (count > 0) { s = format_gcov (count, 0, -1); - if (has_unexecuted_block) + if (has_unexecuted_block + && bbg_supports_has_unexecuted_blocks) { if (flag_use_colors) { pad_count_string (s); - s = SGR_SEQ (COLOR_BG_MAGENTA COLOR_SEPARATOR COLOR_FG_WHITE); + s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA + COLOR_SEPARATOR COLOR_FG_WHITE)); s += SGR_RESET; } else @@ -2535,7 +2891,101 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional, pad_count_string (s); } - fprintf (f, "%s:%5u", s.c_str (), line_num); + /* Format line number in output. */ + char buffer[16]; + sprintf (buffer, "%5u", line_num); + string linestr (buffer); + + if (flag_use_hotness_colors && maximum_count) + { + if (count * 2 > maximum_count) /* > 50%. */ + linestr.insert (0, SGR_SEQ (COLOR_BG_RED)); + else if (count * 5 > maximum_count) /* > 20%. */ + linestr.insert (0, SGR_SEQ (COLOR_BG_YELLOW)); + else if (count * 10 > maximum_count) /* > 10%. */ + linestr.insert (0, SGR_SEQ (COLOR_BG_GREEN)); + linestr += SGR_RESET; + } + + fprintf (f, "%s:%s", s.c_str (), linestr.c_str ()); +} + +static void +print_source_line (FILE *f, const vector &source_lines, + unsigned line) +{ + gcc_assert (line >= 1); + gcc_assert (line <= source_lines.size ()); + + fprintf (f, ":%s\n", source_lines[line - 1]); +} + +/* Output line details for LINE and print it to F file. LINE lives on + LINE_NUM. */ + +static void +output_line_details (FILE *f, const line_info *line, unsigned line_num) +{ + if (flag_all_blocks) + { + arc_info *arc; + int ix, jx; + + ix = jx = 0; + for (vector::const_iterator it = line->blocks.begin (); + it != line->blocks.end (); it++) + { + if (!(*it)->is_call_return) + { + output_line_beginning (f, line->exists, + (*it)->exceptional, false, + (*it)->count, line_num, + "%%%%%", "$$$$$", 0); + fprintf (f, "-block %2d", ix++); + if (flag_verbose) + fprintf (f, " (BB %u)", (*it)->id); + fprintf (f, "\n"); + } + if (flag_branches) + for (arc = (*it)->succ; arc; arc = arc->succ_next) + jx += output_branch_count (f, jx, arc); + } + } + else if (flag_branches) + { + int ix; + + ix = 0; + for (vector::const_iterator it = line->branches.begin (); + it != line->branches.end (); it++) + ix += output_branch_count (f, ix, (*it)); + } +} + +/* Output detail statistics about function FN to file F. */ + +static void +output_function_details (FILE *f, function_info *fn) +{ + if (!flag_branches) + return; + + arc_info *arc = fn->blocks[EXIT_BLOCK].pred; + gcov_type return_count = fn->blocks[EXIT_BLOCK].count; + gcov_type called_count = fn->blocks[ENTRY_BLOCK].count; + + for (; arc; arc = arc->pred_next) + if (arc->fake) + return_count -= arc->count; + + fprintf (f, "function %s", fn->get_name ()); + fprintf (f, " called %s", + format_gcov (called_count, 0, -1)); + fprintf (f, " returned %s", + format_gcov (return_count, called_count, 0)); + fprintf (f, " blocks executed %s", + format_gcov (fn->blocks_executed, fn->get_block_count (), 0)); + fprintf (f, "\n"); } /* Read in the source file one line at a time, and output that line to @@ -2546,12 +2996,27 @@ static void output_lines (FILE *gcov_file, const source_info *src) { #define DEFAULT_LINE_START " -: 0:" +#define FN_SEPARATOR "------------------\n" FILE *source_file; - unsigned line_num; /* current line number. */ - const line_info *line; /* current line info ptr. */ - const char *retval = ""; /* status of source file reading. */ - function_t *fn = NULL; + const char *retval; + + /* Print colorization legend. */ + if (flag_use_colors) + fprintf (gcov_file, "%s", + DEFAULT_LINE_START "Colorization: profile count: " \ + SGR_SEQ (COLOR_BG_CYAN) "zero coverage (exceptional)" SGR_RESET \ + " " \ + SGR_SEQ (COLOR_BG_RED) "zero coverage (unexceptional)" SGR_RESET \ + " " \ + SGR_SEQ (COLOR_BG_MAGENTA) "unexecuted block" SGR_RESET "\n"); + + if (flag_use_hotness_colors) + fprintf (gcov_file, "%s", + DEFAULT_LINE_START "Colorization: line numbers: hotness: " \ + SGR_SEQ (COLOR_BG_RED) "> 50%" SGR_RESET " " \ + SGR_SEQ (COLOR_BG_YELLOW) "> 20%" SGR_RESET " " \ + SGR_SEQ (COLOR_BG_GREEN) "> 10%" SGR_RESET "\n"); fprintf (gcov_file, DEFAULT_LINE_START "Source:%s\n", src->coverage.name); if (!multiple_files) @@ -2561,47 +3026,50 @@ output_lines (FILE *gcov_file, const source_info *src) no_data_file ? "-" : da_file_name); fprintf (gcov_file, DEFAULT_LINE_START "Runs:%u\n", object_runs); } - fprintf (gcov_file, DEFAULT_LINE_START "Programs:%u\n", program_count); source_file = fopen (src->name, "r"); if (!source_file) - { - fnotice (stderr, "Cannot open source file %s\n", src->name); - retval = NULL; - } + fnotice (stderr, "Cannot open source file %s\n", src->name); else if (src->file_time == 0) fprintf (gcov_file, DEFAULT_LINE_START "Source is newer than graph\n"); - if (flag_branches) - fn = src->functions; + vector source_lines; + if (source_file) + while ((retval = read_line (source_file)) != NULL) + source_lines.push_back (xstrdup (retval)); - for (line_num = 1, line = &src->lines[line_num]; - line_num < src->lines.size (); line_num++, line++) + unsigned line_start_group = 0; + vector *fns; + + for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++) { - for (; fn && fn->line == line_num; fn = fn->next_file_fn) + if (line_num >= src->lines.size ()) { - arc_t *arc = fn->blocks[EXIT_BLOCK].pred; - gcov_type return_count = fn->blocks[EXIT_BLOCK].count; - gcov_type called_count = fn->blocks[ENTRY_BLOCK].count; - - for (; arc; arc = arc->pred_next) - if (arc->fake) - return_count -= arc->count; - - fprintf (gcov_file, "function %s", flag_demangled_names ? - fn->demangled_name : fn->name); - fprintf (gcov_file, " called %s", - format_gcov (called_count, 0, -1)); - fprintf (gcov_file, " returned %s", - format_gcov (return_count, called_count, 0)); - fprintf (gcov_file, " blocks executed %s", - format_gcov (fn->blocks_executed, fn->blocks.size () - 2, - 0)); - fprintf (gcov_file, "\n"); + fprintf (gcov_file, "%9s:%5u", "-", line_num); + print_source_line (gcov_file, source_lines, line_num); + continue; } - if (retval) - retval = read_line (source_file); + const line_info *line = &src->lines[line_num]; + + if (line_start_group == 0) + { + fns = src->get_functions_at_location (line_num); + if (fns != NULL && fns->size () > 1) + { + /* It's possible to have functions that partially overlap, + thus take the maximum end_line of functions starting + at LINE_NUM. */ + for (unsigned i = 0; i < fns->size (); i++) + if ((*fns)[i]->end_line > line_start_group) + line_start_group = (*fns)[i]->end_line; + } + else if (fns != NULL && fns->size () == 1) + { + function_info *fn = (*fns)[0]; + output_function_details (gcov_file, fn); + } + } /* For lines which don't exist in the .bb file, print '-' before the source line. For lines which exist but were never @@ -2610,54 +3078,63 @@ output_lines (FILE *gcov_file, const source_info *src) There are 16 spaces of indentation added before the source line so that tabs won't be messed up. */ output_line_beginning (gcov_file, line->exists, line->unexceptional, - line->has_unexecuted_block, line->count, line_num, - "=====", "#####"); - fprintf (gcov_file, ":%s\n", retval ? retval : "/*EOF*/"); + line->has_unexecuted_block, line->count, + line_num, "=====", "#####", src->maximum_count); - if (flag_all_blocks) - { - arc_t *arc; - int ix, jx; + print_source_line (gcov_file, source_lines, line_num); + output_line_details (gcov_file, line, line_num); - ix = jx = 0; - for (vector::const_iterator it = line->blocks.begin (); - it != line->blocks.end (); it++) + if (line_start_group == line_num) + { + for (vector::iterator it = fns->begin (); + it != fns->end (); it++) { - if (!(*it)->is_call_return) + function_info *fn = *it; + vector &lines = fn->lines; + + fprintf (gcov_file, FN_SEPARATOR); + + string fn_name = fn->get_name (); + if (flag_use_colors) + { + fn_name.insert (0, SGR_SEQ (COLOR_FG_CYAN)); + fn_name += SGR_RESET; + } + + fprintf (gcov_file, "%s:\n", fn_name.c_str ()); + + output_function_details (gcov_file, fn); + + /* Print all lines covered by the function. */ + for (unsigned i = 0; i < lines.size (); i++) { + line_info *line = &lines[i]; + unsigned l = fn->start_line + i; + + /* For lines which don't exist in the .bb file, print '-' + before the source line. For lines which exist but + were never executed, print '#####' or '=====' before + the source line. Otherwise, print the execution count + before the source line. + There are 16 spaces of indentation added before the source + line so that tabs won't be messed up. */ output_line_beginning (gcov_file, line->exists, - (*it)->exceptional, false, - (*it)->count, line_num, - "%%%%%", "$$$$$"); - fprintf (gcov_file, "-block %2d", ix++); - if (flag_verbose) - fprintf (gcov_file, " (BB %u)", (*it)->id); - fprintf (gcov_file, "\n"); + line->unexceptional, + line->has_unexecuted_block, + line->count, + l, "=====", "#####", + src->maximum_count); + + print_source_line (gcov_file, source_lines, l); + output_line_details (gcov_file, line, l); } - if (flag_branches) - for (arc = (*it)->succ; arc; arc = arc->succ_next) - jx += output_branch_count (gcov_file, jx, arc); } - } - else if (flag_branches) - { - int ix; - ix = 0; - for (vector::const_iterator it = line->branches.begin (); - it != line->branches.end (); it++) - ix += output_branch_count (gcov_file, ix, (*it)); + fprintf (gcov_file, FN_SEPARATOR); + line_start_group = 0; } } - /* Handle all remaining source lines. There may be lines after the - last line of code. */ - if (retval) - { - for (; (retval = read_line (source_file)); line_num++) - fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval); - } - if (source_file) fclose (source_file); }