/* Gcov.c: prepend line execution counts and branch probabilities to a
source file.
- Copyright (C) 1990-2017 Free Software Foundation, Inc.
+ Copyright (C) 1990-2020 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>
#define INCLUDE_ALGORITHM
#define INCLUDE_VECTOR
#define INCLUDE_STRING
+#define INCLUDE_MAP
+#define INCLUDE_SET
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "version.h"
#include "demangle.h"
#include "color-macros.h"
+#include "pretty-print.h"
+#include "json.h"
+#include <zlib.h>
#include <getopt.h>
#include "md5.h"
/* This is the size of the buffer used to read in source file lines. */
-struct function_info;
-struct block_info;
-struct source_info;
+class function_info;
+class block_info;
+class source_info;
/* Describes an arc between two basic blocks. */
-typedef struct arc_info
+struct arc_info
{
/* source and destination blocks. */
- struct block_info *src;
- struct block_info *dst;
+ class block_info *src;
+ class block_info *dst;
/* transition counts. */
gcov_type count;
/* 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 which locations (lines and files) are associated with
a basic block. */
-struct block_location_info
+class block_location_info
{
+public:
block_location_info (unsigned _source_file_idx):
source_file_idx (_source_file_idx)
{}
/* Describes a basic block. Contains lists of arcs to successor and
predecessor blocks. */
-typedef struct block_info
+class block_info
{
+public:
/* 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;
{
/* 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. */
/* Temporary chain for solving graph, and for chaining blocks on one
line. */
- struct block_info *chain;
+ class 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),
cycle.arc = NULL;
}
+/* Describes a single line of source. Contains a chain of basic blocks
+ with code on it. */
+
+class line_info
+{
+public:
+ /* 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<arc_info *> branches;
+
+ /* blocks which start on this line. Used in all-blocks mode. */
+ vector<block_info *> 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
+class function_info
{
+public:
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;
/* 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<block_t> blocks;
+ vector<block_info> blocks;
unsigned blocks_executed;
/* Raw arc coverage counts. */
- gcov_type *counts;
- unsigned num_counts;
+ vector<gcov_type> counts;
+
+ /* First line number. */
+ unsigned start_line;
- /* First line number & file. */
- unsigned line;
+ /* First line column. */
+ unsigned start_column;
+
+ /* Last line number. */
+ unsigned end_line;
+
+ /* Last line column. */
+ unsigned end_column;
+
+ /* 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<line_info> lines;
/* Next function. */
- struct function_info *next;
-} function_t;
+ class function_info *next;
+
+ /* 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;
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
-{
- /* Return true when NEEDLE is one of basic blocks the line belongs to. */
- bool has_block (block_t *needle);
-
- gcov_type count; /* execution count */
- arc_t *branches; /* branches from blocks that end on this line. */
- block_t *blocks; /* blocks which start on this line.
- Used in all-blocks mode. */
- unsigned exists : 1;
- unsigned unexceptional : 1;
- unsigned has_unexecuted_block : 1;
-} line_t;
-
-bool
-line_t::has_block (block_t *needle)
-{
- for (block_t *n = blocks; n; n = n->chain)
- if (n == needle)
- return true;
-
- return false;
-}
+};
/* Describes a file mentioned in the block graph. Contains an array
of line info. */
-struct source_info
+class source_info
{
+public:
/* Default constructor. */
source_info ();
+ vector<function_info *> *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;
/* Vector of line information. */
- vector<line_t> lines;
+ vector<line_info> 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<function_info *> functions;
+
+ /* Line number to functions map. */
+ vector<vector<function_info *> *> 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 ()
{
}
-typedef struct name_map
+/* Register a new function. */
+void
+source_info::add_function (function_info *fn)
{
- char *name; /* Source file name */
+ functions.push_back (fn);
+
+ if (fn->start_line >= line_to_function_map.size ())
+ line_to_function_map.resize (fn->start_line + 1);
+
+ vector<function_info *> **slot = &line_to_function_map[fn->start_line];
+ if (*slot == NULL)
+ *slot = new vector<function_info *> ();
+
+ (*slot)->push_back (fn);
+}
+
+vector<function_info *> *
+source_info::get_functions_at_location (unsigned line_num) const
+{
+ if (line_num >= line_to_function_map.size ())
+ return NULL;
+
+ vector<function_info *> *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:
+ name_map ()
+ {
+ }
+
+ name_map (char *_name, unsigned _src): name (_name), src (_src)
+ {
+ }
+
+ bool operator== (const name_map &rhs) const
+ {
+#if HAVE_DOS_BASED_FILE_SYSTEM
+ return strcasecmp (this->name, rhs.name) == 0;
+#else
+ return strcmp (this->name, rhs.name) == 0;
+#endif
+ }
+
+ bool operator< (const name_map &rhs) const
+ {
+#if HAVE_DOS_BASED_FILE_SYSTEM
+ return strcasecmp (this->name, rhs.name) < 0;
+#else
+ return strcmp (this->name, rhs.name) < 0;
+#endif
+ }
+
+ const char *name; /* Source file name */
unsigned src; /* Source file */
-} name_map_t;
+};
-/* Holds a list of function basic block graphs. */
+/* Vector of all functions. */
+static vector<function_info *> functions;
-static function_t *functions;
-static function_t **fn_end = &functions;
+/* Function ident to function_info * map. */
+static map<unsigned, function_info *> ident_to_fn;
/* Vector of source files. */
static vector<source_info> 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 */
+/* Mapping of file names to sources */
+static vector<name_map> names;
+
+/* Record all processed files in order to warn about
+ a file being read multiple times. */
+static vector<char *> processed_files;
/* This holds data summary information. */
static unsigned object_runs;
-static unsigned program_count;
static unsigned total_lines;
static unsigned total_executed;
/* 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;
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,
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. */
static int flag_all_blocks = 0;
+/* Output human readable numbers. */
+
+static int flag_human_readable_numbers = 0;
+
/* Output summary info for each function. */
static int flag_function_summary = 0;
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 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 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 *);
+static void file_summary (const coverage_info *);
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 (0), end_line (0), end_column (0),
+ src (0), lines (), next (NULL)
{
}
{
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)
{
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!
simple paths)--the node is unblocked only when it participates in a cycle.
*/
-typedef vector<arc_t *> arc_vector_t;
-typedef vector<const block_t *> block_vector_t;
-
-/* Enum with types of loop in CFG. */
-
-enum loop_type
-{
- NO_LOOP = 0,
- LOOP = 1,
- NEGATIVE_LOOP = 3
-};
-
-/* Loop_type operator that merges two values: A and B. */
-
-inline loop_type& operator |= (loop_type& a, loop_type b)
-{
- return a = static_cast<loop_type> (a | b);
-}
+typedef vector<arc_info *> arc_vector_t;
+typedef vector<const block_info *> block_vector_t;
/* Handle cycle identified by EDGES, where the function finds minimum cs_count
and subtract the value from all counts. The subtracted value is added
to COUNT. Returns type of loop. */
-static loop_type
+static void
handle_cycle (const arc_vector_t &edges, int64_t &count)
{
/* Find the minimum edge of the cycle, and reduce all nodes in the cycle by
for (unsigned i = 0; i < edges.size (); i++)
edges[i]->cs_count -= cycle_count;
- return cycle_count < 0 ? NEGATIVE_LOOP : LOOP;
+ gcc_assert (cycle_count > 0);
}
/* Unblock a block U from BLOCKED. Apart from that, iterate all blocks
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_vector_t > &block_lists)
{
block_vector_t::iterator it = find (blocked.begin (), blocked.end (), u);
unblock (*it, blocked, block_lists);
}
+/* Return true when PATH contains a zero cycle arc count. */
+
+static bool
+path_contains_zero_or_negative_cycle_arc (arc_vector_t &path)
+{
+ for (unsigned i = 0; i < path.size (); i++)
+ if (path[i]->cs_count <= 0)
+ return true;
+ return false;
+}
+
/* Find circuit going to block V, PATH is provisional seen cycle.
BLOCKED is vector of blocked vertices, BLOCK_LISTS contains vertices
blocked by a block. COUNT is accumulated count of the current LINE.
Returns what type of loop it contains. */
-static loop_type
-circuit (block_t *v, arc_vector_t &path, block_t *start,
+static bool
+circuit (block_info *v, arc_vector_t &path, block_info *start,
block_vector_t &blocked, vector<block_vector_t> &block_lists,
- line_t &linfo, int64_t &count)
+ line_info &linfo, int64_t &count)
{
- loop_type result = NO_LOOP;
+ bool loop_found = false;
/* Add v to the block list. */
gcc_assert (find (blocked.begin (), blocked.end (), v) == blocked.end ());
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;
- if (w < start || !linfo.has_block (w))
+ block_info *w = arc->dst;
+ if (w < start
+ || arc->cs_count <= 0
+ || !linfo.has_block (w))
continue;
path.push_back (arc);
if (w == start)
- /* Cycle has been found. */
- result |= handle_cycle (path, count);
- else if (find (blocked.begin (), blocked.end (), w) == blocked.end ())
- result |= circuit (w, path, start, blocked, block_lists, linfo, count);
+ {
+ /* Cycle has been found. */
+ handle_cycle (path, count);
+ loop_found = true;
+ }
+ else if (!path_contains_zero_or_negative_cycle_arc (path)
+ && find (blocked.begin (), blocked.end (), w) == blocked.end ())
+ loop_found |= circuit (w, path, start, blocked, block_lists, linfo,
+ count);
path.pop_back ();
}
- if (result != NO_LOOP)
+ if (loop_found)
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;
- if (w < start || !linfo.has_block (w))
+ block_info *w = arc->dst;
+ if (w < start
+ || arc->cs_count <= 0
+ || !linfo.has_block (w))
continue;
size_t index
list.push_back (v);
}
- return result;
+ return loop_found;
}
-/* Find cycles for a LINFO. If HANDLE_NEGATIVE_CYCLES is set and the line
- contains a negative loop, then perform the same function once again. */
+/* Find cycles for a LINFO. */
static gcov_type
-get_cycles_count (line_t &linfo, bool handle_negative_cycles = true)
+get_cycles_count (line_info &linfo)
{
/* Note that this algorithm works even if blocks aren't in sorted order.
Each iteration of the circuit detection is completely independent
Therefore, operating on a permuted order (i.e., non-sorted) only
has the effect of permuting the output cycles. */
- loop_type result = NO_LOOP;
+ bool loop_found = false;
gcov_type count = 0;
- for (block_t *block = linfo.blocks; block; block = block->chain)
+ for (vector<block_info *>::iterator it = linfo.blocks.begin ();
+ it != linfo.blocks.end (); it++)
{
arc_vector_t path;
block_vector_t blocked;
vector<block_vector_t > block_lists;
- result |= circuit (block, path, block, blocked, block_lists, linfo,
- count);
+ loop_found |= circuit (*it, path, *it, blocked, block_lists, linfo,
+ count);
}
- /* If we have a negative cycle, repeat the find_cycles routine. */
- if (result == NEGATIVE_LOOP && handle_negative_cycles)
- count += get_cycles_count (linfo, false);
-
return count;
}
/* Handle response files. */
expandargv (&argc, &argv);
- a_names = 10;
- names = XNEWVEC (name_map_t, a_names);
-
argno = process_args (argc, argv);
if (optind == argc)
print_usage (true);
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;
}
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\
source files\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, " -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");
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 2020 Free Software Foundation, Inc.\n",
_("(C)"));
fnotice (stdout,
_("This is free software; see the source for copying conditions.\n"
{ "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' },
{ "function-summaries", no_argument, NULL, 'f' },
{ "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 }
};
{
int opt;
- const char *opts = "abcdfhiklmno:prs:uvwx";
+ const char *opts = "abcdfhijklmno:pqrs:tuvwx";
while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
{
switch (opt)
case 'l':
flag_long_names = 1;
break;
+ case 'j':
+ flag_human_readable_numbers = 1;
+ break;
case 'k':
flag_use_colors = 1;
break;
+ case 'q':
+ flag_use_hotness_colors = 1;
+ break;
case 'm':
flag_demangled_names = 1;
break;
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. */
return optind;
}
-/* Output the result in intermediate format used by 'lcov'.
+/* Output intermediate LINE sitting on LINE_NUM to JSON OBJECT.
+ Add FUNCTION_NAME to the LINE. */
-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,
+ const char *function_name)
+{
+ if (!line->exists)
+ return;
-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. */
+ json::object *lineo = new json::object ();
+ lineo->set ("line_number", new json::integer_number (line_num));
+ if (function_name != NULL)
+ lineo->set ("function_name", new json::string (function_name));
+ lineo->set ("count", new json::integer_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<arc_info *>::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::integer_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);
+}
+
+/* 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)
-{
- 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->next_file_fn)
- {
- /* 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->lines.size ();
- line_num++, line++)
- {
- arc_t *arc;
- 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 (arc = line->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);
- }
- }
+output_json_intermediate_file (json::array *json_files, source_info *src)
+{
+ json::object *root = new json::object ();
+ json_files->append (root);
+
+ root->set ("file", new json::string (src->name));
+
+ json::array *functions = new json::array ();
+ root->set ("functions", functions);
+
+ std::sort (src->functions.begin (), src->functions.end (),
+ function_line_start_cmp ());
+ for (vector<function_info *>::iterator it = src->functions.begin ();
+ it != src->functions.end (); it++)
+ {
+ 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::integer_number ((*it)->start_line));
+ function->set ("start_column",
+ new json::integer_number ((*it)->start_column));
+ function->set ("end_line", new json::integer_number ((*it)->end_line));
+ function->set ("end_column",
+ new json::integer_number ((*it)->end_column));
+ function->set ("blocks",
+ new json::integer_number ((*it)->get_block_count ()));
+ function->set ("blocks_executed",
+ new json::integer_number ((*it)->blocks_executed));
+ function->set ("execution_count",
+ new json::integer_number ((*it)->blocks[0].count));
+
+ functions->append (function);
+ }
+
+ json::array *lineso = new json::array ();
+ root->set ("lines", lineso);
+
+ function_info *last_non_group_fn = NULL;
+
+ for (unsigned line_num = 1; line_num <= src->lines.size (); line_num++)
+ {
+ vector<function_info *> *fns = src->get_functions_at_location (line_num);
+
+ if (fns != NULL)
+ /* Print first group functions that begin on the line. */
+ for (vector<function_info *>::iterator it2 = fns->begin ();
+ it2 != fns->end (); it2++)
+ {
+ if (!(*it2)->is_group)
+ last_non_group_fn = *it2;
+
+ vector<line_info> &lines = (*it2)->lines;
+ for (unsigned i = 0; i < lines.size (); i++)
+ {
+ line_info *line = &lines[i];
+ output_intermediate_json_line (lineso, line, line_num + i,
+ (*it2)->m_name);
+ }
+ }
+
+ /* 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,
+ (last_non_group_fn != NULL
+ ? last_non_group_fn->m_name : NULL));
}
}
+/* 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 <function_start>
+{
+ 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<function_start_pair_hash, function_info *> fn_map;
+
+ /* Identify group functions. */
+ for (vector<function_info *>::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<function_info *>::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. */
+ }
}
}
{
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
{
static void
generate_results (const char *file_name)
{
- function_t *fn;
+ char *gcov_intermediate_filename;
- for (fn = functions; fn; fn = fn->next)
+ for (vector<function_info *>::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)
{
- function_summary (&coverage, "Function");
+ function_summary (&coverage);
fnotice (stdout, "\n");
}
}
+ name_map needle;
+
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;
+ needle.name = file_name;
+ vector<name_map>::iterator it = std::find (names.begin (), names.end (),
+ needle);
+ if (it != names.end ())
+ file_name = sources[it->src].coverage.name;
else
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));
+ root->set ("data_file", new json::string (file_name));
+
+ json::array *json_files = new json::array ();
+ root->set ("files", json_files);
+
for (vector<source_info>::iterator it = sources.begin ();
it != sources.end (); it++)
{
}
accumulate_line_counts (src);
- function_summary (&src->coverage, "File");
+
+ if (!flag_use_stdout)
+ file_summary (&src->coverage);
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)
static void
release_structures (void)
{
- unsigned ix;
- function_t *fn;
-
- for (ix = n_names; ix--;)
- free (names[ix].name);
- free (names);
-
- while ((fn = functions))
- {
- functions = fn->next;
- delete fn;
- }
+ for (vector<function_info *>::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
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)
+ name_map needle;
+ needle.name = file_name;
+
+ vector<name_map>::iterator it = std::find (names.begin (), names.end (),
+ needle);
+ if (it != names.end ())
{
- /* 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;
+ idx = it->src;
+ goto check_date;
}
/* 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)
+ needle.name = canon;
+ it = std::find (names.begin (), names.end (), needle);
+ if (it == names.end ())
{
/* Not found with canonical name, create a new source. */
source_info *src;
idx = sources.size ();
- name_map = &names[n_names++];
- name_map->name = canon;
- name_map->src = idx;
+ needle = name_map (canon, idx);
+ names.push_back (needle);
sources.push_back (source_info ());
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
src->file_time = status.st_mtime;
}
else
- idx = name_map->src;
+ idx = it->src;
- if (name_search (file_name, name_map))
+ needle.name = file_name;
+ if (std::find (names.begin (), names.end (), needle) == names.end ())
{
/* Append the non-canonical name. */
- name_map = &names[n_names++];
- name_map->name = xstrdup (file_name);
- name_map->src = idx;
+ names.push_back (name_map (xstrdup (file_name), idx));
}
/* Resort the name map. */
- qsort (names, n_names, sizeof (*names), name_sort);
+ std::sort (names.begin (), names.end ());
check_date:
if (sources[idx].file_time > bbg_file_time)
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 ();
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 ();
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 ();
+ unsigned end_column = 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->end_column = end_column;
+ 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 ());
}
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;
if (dest >= fn->blocks.size ())
goto corrupt;
- arc = XCNEW (arc_t);
+ arc = XCNEW (arc_info);
arc->dst = &fn->blocks[dest];
arc->src = src_blk;
}
if (!arc->on_tree)
- fn->num_counts++;
+ fn->counts.push_back (0);
}
if (mark_catches)
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;
}
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<unsigned, function_info *>::iterator it;
if (!gcov_open (da_file_name, 1))
{
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)
;
{
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);
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)
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. */
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. */
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;
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);)
while ((blk = invalid_blocks))
{
gcov_type total = 0;
- const arc_t *arc;
+ const arc_info *arc;
invalid_blocks = blk->chain;
blk->invalid_chain = 0;
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;
}
if (blk->num_pred == 1)
{
- block_t *src;
+ block_info *src;
total = blk->count;
inv_arc = NULL;
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;
}
}
/* 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--;)
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)
/* 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)
{
}
}
-/* 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. */
+/* Format COUNT, if flag_human_readable_numbers is set, return it human
+ readable format. */
static char const *
-format_gcov (gcov_type top, gcov_type bottom, int dp)
+format_count (gcov_type count)
{
- static char buffer[20];
+ static char buffer[64];
+ const char *units = " kMGTPEZY";
- /* Handle invalid values that would result in a misleading value. */
- if (bottom != 0 && top > bottom && dp >= 0)
+ if (count < 1000 || !flag_human_readable_numbers)
{
- sprintf (buffer, "NAN %%");
+ sprintf (buffer, "%" PRId64, count);
return buffer;
}
- if (dp >= 0)
+ unsigned i;
+ gcov_type divisor = 1;
+ for (i = 0; units[i+1]; i++, divisor *= 1000)
{
- 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] = '.';
- }
+ if (count + divisor / 2 < 1000 * divisor)
+ break;
+ }
+ 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 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 decimal_places)
+{
+ static char buffer[20];
+
+ if (decimal_places >= 0)
+ {
+ float ratio = bottom ? 100.0f * top / bottom: 0;
+
+ /* 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
- sprintf (buffer, "%" PRId64, (int64_t)top);
+ return format_count (top);
return buffer;
}
fnotice (stdout, "No executable lines\n");
}
-/* Output summary info for a function or file. */
+/* Output summary info for a function. */
static void
-function_summary (const coverage_t *coverage, const char *title)
+function_summary (const coverage_info *coverage)
{
- fnotice (stdout, "%s '%s'\n", title, coverage->name);
+ fnotice (stdout, "%s '%s'\n", "Function", coverage->name);
+ executed_summary (coverage->lines, coverage->lines_executed);
+}
+
+/* Output summary info for a file. */
+
+static void
+file_summary (const coverage_info *coverage)
+{
+ fnotice (stdout, "%s '%s'\n", "File", coverage->name);
executed_summary (coverage->lines, coverage->lines_executed);
if (flag_branches)
return result;
}
+/* Mangle BASE name, copy it at the beginning of PTR buffer and
+ return address of the \0 character of the buffer. */
+
static char *
mangle_name (char const *base, char *ptr)
{
/* Generate the source filename part. */
if (!flag_preserve_paths)
- {
- base = lbasename (base);
- len = strlen (base);
- memcpy (ptr, base, len);
- ptr += len;
- }
+ base = lbasename (base);
else
- {
- /* Convert '/' to '#', convert '..' to '^',
- convert ':' to '~' on DOS based file system. */
- const char *probe;
+ base = mangle_path (base);
-#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++;
- }
- }
- }
+ len = strlen (base);
+ memcpy (ptr, base, len);
+ ptr += len;
return 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_t *line = NULL;
- block_t *block = &fn->blocks[ix];
+ line_info *line = NULL;
+ 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<unsigned> &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)
- {
- block->chain = line->blocks;
- line->blocks = 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)
{
- arc->line_next = line->branches;
- line->branches = 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<arc_info *>::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<block_info *>::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. */
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<function_info *>::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<line_info>::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<line_t>::reverse_iterator it = src->lines.rbegin ();
- it != src->lines.rend (); it++)
- {
- line_t *line = &(*it);
- if (line->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->blocks, block_p = NULL; block;
- block_p = block, block = block_n)
- {
- block_n = block->chain;
- block->chain = block_p;
- block->cycle.ident = ix;
- }
- line->blocks = block_p;
+ /* Work on global lines that line in source file SRC. */
+ for (vector<line_info>::iterator it = src->lines.begin ();
+ it != src->lines.end (); it++)
+ accumulate_line_info (&(*it), src, true);
- /* Sum the entry arcs. */
- for (block = line->blocks; block; block = block->chain)
- {
- arc_t *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<function_info *>::iterator it = src->functions.begin ();
+ it != src->functions.end (); it++)
+ {
+ function_info *fn = *it;
- for (arc = block->pred; arc; arc = arc->pred_next)
- if (flag_branches)
- add_branch_counts (&src->coverage, arc);
- }
+ if (fn->src != src->index || !fn->is_group)
+ continue;
- /* Cycle detection. */
- for (block = line->blocks; block; block = block->chain)
- {
- for (arc_t *arc = block->pred; arc; arc = arc->pred_next)
- if (!line->has_block (arc->src))
- count += arc->count;
- for (arc_t *arc = block->succ; arc; arc = arc->succ_next)
- arc->cs_count = arc->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];
- /* Now, add the count of loops entirely on this line. */
- count += get_cycles_count (*line);
- line->count = count;
- }
+ if (!src_line->exists)
+ src->coverage.lines++;
+ if (!src_line->count && fn_line->count)
+ src->coverage.lines_executed++;
- if (line->exists)
- {
- src->coverage.lines++;
- if (line->count)
- src->coverage.lines_executed++;
- }
+ src_line->count += fn_line->count;
+ src_line->exists = 1;
- ix++;
- }
+ 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)
{
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)
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
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<const char *> &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<block_info *>::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<arc_info *>::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
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_t *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)
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<const char *> 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<function_info *> *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
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)
- {
- block_t *block;
- arc_t *arc;
- int ix, jx;
+ print_source_line (gcov_file, source_lines, line_num);
+ output_line_details (gcov_file, line, line_num);
- for (ix = jx = 0, block = line->blocks; block;
- block = block->chain)
+ if (line_start_group == line_num)
+ {
+ for (vector<function_info *>::iterator it = fns->begin ();
+ it != fns->end (); it++)
{
- if (!block->is_call_return)
+ function_info *fn = *it;
+ vector<line_info> &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,
- block->exceptional, false,
- block->count, line_num,
- "%%%%%", "$$$$$");
- fprintf (gcov_file, "-block %2d", ix++);
- if (flag_verbose)
- fprintf (gcov_file, " (BB %u)", block->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 = 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->branches; arc; arc = arc->line_next)
- ix += output_branch_count (gcov_file, ix, arc);
+ 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);
}