3262 lines
86 KiB
C++
3262 lines
86 KiB
C++
/* Gcov.c: prepend line execution counts and branch probabilities to a
|
||
source file.
|
||
Copyright (C) 1990-2022 Free Software Foundation, Inc.
|
||
Contributed by James E. Wilson of Cygnus Support.
|
||
Mangled by Bob Manson of Cygnus Support.
|
||
Mangled further by Nathan Sidwell <nathan@codesourcery.com>
|
||
|
||
Gcov is free software; you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation; either version 3, or (at your option)
|
||
any later version.
|
||
|
||
Gcov is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with Gcov; see the file COPYING3. If not see
|
||
<http://www.gnu.org/licenses/>. */
|
||
|
||
/* ??? Print a list of the ten blocks with the highest execution counts,
|
||
and list the line numbers corresponding to those blocks. Also, perhaps
|
||
list the line numbers with the highest execution counts, only printing
|
||
the first if there are several which are all listed in the same block. */
|
||
|
||
/* ??? Should have an option to print the number of basic blocks, and the
|
||
percent of them that are covered. */
|
||
|
||
/* Need an option to show individual block counts, and show
|
||
probabilities of fall through arcs. */
|
||
|
||
#include "config.h"
|
||
#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 "intl.h"
|
||
#include "diagnostic.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"
|
||
|
||
using namespace std;
|
||
|
||
#define IN_GCOV 1
|
||
#include "gcov-io.h"
|
||
#include "gcov-io.cc"
|
||
|
||
/* The gcno file is generated by -ftest-coverage option. The gcda file is
|
||
generated by a program compiled with -fprofile-arcs. Their formats
|
||
are documented in gcov-io.h. */
|
||
|
||
/* The functions in this file for creating and solution program flow graphs
|
||
are very similar to functions in the gcc source file profile.cc. In
|
||
some places we make use of the knowledge of how profile.cc works to
|
||
select particular algorithms here. */
|
||
|
||
/* The code validates that the profile information read in corresponds
|
||
to the code currently being compiled. Rather than checking for
|
||
identical files, the code below compares a checksum on the CFG
|
||
(based on the order of basic blocks and the arcs in the CFG). If
|
||
the CFG checksum in the gcda file match the CFG checksum in the
|
||
gcno file, the profile data will be used. */
|
||
|
||
/* This is the size of the buffer used to read in source file lines. */
|
||
|
||
class function_info;
|
||
class block_info;
|
||
class source_info;
|
||
|
||
/* Describes an arc between two basic blocks. */
|
||
|
||
struct arc_info
|
||
{
|
||
/* source and destination blocks. */
|
||
class block_info *src;
|
||
class block_info *dst;
|
||
|
||
/* transition counts. */
|
||
gcov_type count;
|
||
/* used in cycle search, so that we do not clobber original counts. */
|
||
gcov_type cs_count;
|
||
|
||
unsigned int count_valid : 1;
|
||
unsigned int on_tree : 1;
|
||
unsigned int fake : 1;
|
||
unsigned int fall_through : 1;
|
||
|
||
/* Arc to a catch handler. */
|
||
unsigned int is_throw : 1;
|
||
|
||
/* Arc is for a function that abnormally returns. */
|
||
unsigned int is_call_non_return : 1;
|
||
|
||
/* Arc is for catch/setjmp. */
|
||
unsigned int is_nonlocal_return : 1;
|
||
|
||
/* Is an unconditional branch. */
|
||
unsigned int is_unconditional : 1;
|
||
|
||
/* Loop making arc. */
|
||
unsigned int cycle : 1;
|
||
|
||
/* Links to next arc on src and dst lists. */
|
||
struct arc_info *succ_next;
|
||
struct arc_info *pred_next;
|
||
};
|
||
|
||
/* Describes which locations (lines and files) are associated with
|
||
a basic block. */
|
||
|
||
class block_location_info
|
||
{
|
||
public:
|
||
block_location_info (unsigned _source_file_idx):
|
||
source_file_idx (_source_file_idx)
|
||
{}
|
||
|
||
unsigned source_file_idx;
|
||
vector<unsigned> lines;
|
||
};
|
||
|
||
/* Describes a basic block. Contains lists of arcs to successor and
|
||
predecessor blocks. */
|
||
|
||
class block_info
|
||
{
|
||
public:
|
||
/* Constructor. */
|
||
block_info ();
|
||
|
||
/* Chain of exit and entry arcs. */
|
||
arc_info *succ;
|
||
arc_info *pred;
|
||
|
||
/* Number of unprocessed exit and entry arcs. */
|
||
gcov_type num_succ;
|
||
gcov_type num_pred;
|
||
|
||
unsigned id;
|
||
|
||
/* Block execution count. */
|
||
gcov_type count;
|
||
unsigned count_valid : 1;
|
||
unsigned valid_chain : 1;
|
||
unsigned invalid_chain : 1;
|
||
unsigned exceptional : 1;
|
||
|
||
/* Block is a call instrumenting site. */
|
||
unsigned is_call_site : 1; /* Does the call. */
|
||
unsigned is_call_return : 1; /* Is the return. */
|
||
|
||
/* Block is a landing pad for longjmp or throw. */
|
||
unsigned is_nonlocal_return : 1;
|
||
|
||
vector<block_location_info> locations;
|
||
|
||
struct
|
||
{
|
||
/* Single line graph cycle workspace. Used for all-blocks
|
||
mode. */
|
||
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. */
|
||
class block_info *chain;
|
||
|
||
};
|
||
|
||
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),
|
||
exceptional (0), is_call_site (0), is_call_return (0), is_nonlocal_return (0),
|
||
locations (), chain (NULL)
|
||
{
|
||
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. */
|
||
|
||
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 *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_info> blocks;
|
||
unsigned blocks_executed;
|
||
|
||
/* Raw arc coverage counts. */
|
||
vector<gcov_type> counts;
|
||
|
||
/* First line number. */
|
||
unsigned start_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;
|
||
|
||
/* Vector of line information (used only for group functions). */
|
||
vector<line_info> lines;
|
||
|
||
/* Next function. */
|
||
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. */
|
||
|
||
struct coverage_info
|
||
{
|
||
int lines;
|
||
int lines_executed;
|
||
|
||
int branches;
|
||
int branches_executed;
|
||
int branches_taken;
|
||
|
||
int calls;
|
||
int calls_executed;
|
||
|
||
char *name;
|
||
};
|
||
|
||
/* Describes a file mentioned in the block graph. Contains an array
|
||
of line 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);
|
||
|
||
/* Debug the source file. */
|
||
void debug ();
|
||
|
||
/* 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_info> lines;
|
||
|
||
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. */
|
||
vector<function_info *> functions;
|
||
|
||
/* Line number to functions map. */
|
||
vector<vector<function_info *> *> line_to_function_map;
|
||
};
|
||
|
||
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<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;
|
||
}
|
||
|
||
void source_info::debug ()
|
||
{
|
||
fprintf (stderr, "source_info: %s\n", name);
|
||
for (vector<function_info *>::iterator it = functions.begin ();
|
||
it != functions.end (); it++)
|
||
{
|
||
function_info *fn = *it;
|
||
fprintf (stderr, " function_info: %s\n", fn->get_name ());
|
||
for (vector<block_info>::iterator bit = fn->blocks.begin ();
|
||
bit != fn->blocks.end (); bit++)
|
||
{
|
||
fprintf (stderr, " block_info id=%d, count=%" PRId64 " \n",
|
||
bit->id, bit->count);
|
||
}
|
||
}
|
||
|
||
for (unsigned lineno = 1; lineno < lines.size (); ++lineno)
|
||
{
|
||
line_info &line = lines[lineno];
|
||
fprintf (stderr, " line_info=%d, count=%" PRId64 "\n", lineno, line.count);
|
||
}
|
||
|
||
fprintf (stderr, "\n");
|
||
}
|
||
|
||
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 */
|
||
};
|
||
|
||
/* Vector of all functions. */
|
||
static vector<function_info *> functions;
|
||
|
||
/* Function ident to function_info * map. */
|
||
static map<unsigned, function_info *> ident_to_fn;
|
||
|
||
/* Vector of source files. */
|
||
static vector<source_info> sources;
|
||
|
||
/* 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 total_lines;
|
||
static unsigned total_executed;
|
||
|
||
/* Modification time of graph file. */
|
||
|
||
static time_t bbg_file_time;
|
||
|
||
/* Name of the notes (gcno) output file. The "bbg" prefix is for
|
||
historical reasons, when the notes file contained only the
|
||
basic block graph notes. */
|
||
|
||
static char *bbg_file_name;
|
||
|
||
/* Stamp of the bbg file */
|
||
static unsigned bbg_stamp;
|
||
|
||
/* 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;
|
||
|
||
/* Data file is missing. */
|
||
|
||
static int no_data_file;
|
||
|
||
/* If there is several input files, compute and display results after
|
||
reading all data files. This way if two or more gcda file refer to
|
||
the same source file (eg inline subprograms in a .h file), the
|
||
counts are added. */
|
||
|
||
static int multiple_files = 0;
|
||
|
||
/* Output branch probabilities. */
|
||
|
||
static int flag_branches = 0;
|
||
|
||
/* Show unconditional branches too. */
|
||
static int flag_unconditional = 0;
|
||
|
||
/* Output a gcov file if this is true. This is on by default, and can
|
||
be turned off by the -n option. */
|
||
|
||
static int flag_gcov_file = 1;
|
||
|
||
/* Output 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 JSON intermediate format used by consumers. */
|
||
|
||
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,
|
||
then the output file name is a.c##x.h.gcov instead of x.h.gcov. */
|
||
|
||
static int flag_long_names = 0;
|
||
|
||
/* For situations when a long name can potentially hit filesystem path limit,
|
||
let's calculate md5sum of the path and append it to a file name. */
|
||
|
||
static int flag_hash_filenames = 0;
|
||
|
||
/* Print verbose informations. */
|
||
|
||
static int flag_verbose = 0;
|
||
|
||
/* Print colored output. */
|
||
|
||
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;
|
||
|
||
/* Print debugging dumps. */
|
||
|
||
static int flag_debug = 0;
|
||
|
||
/* Object directory file prefix. This is the directory/file where the
|
||
graph and data files are looked for, if nonzero. */
|
||
|
||
static char *object_directory = 0;
|
||
|
||
/* Source directory prefix. This is removed from source pathnames
|
||
that match, when generating the output file name. */
|
||
|
||
static char *source_prefix = 0;
|
||
static size_t source_length = 0;
|
||
|
||
/* Only show data for sources with relative pathnames. Absolute ones
|
||
usually indicate a system header file, which although it may
|
||
contain inline functions, is usually uninteresting. */
|
||
static int flag_relative_only = 0;
|
||
|
||
/* Preserve all pathname components. Needed when object files and
|
||
source files are in subdirectories. '/' is mangled as '#', '.' is
|
||
elided and '..' mangled to '^'. */
|
||
|
||
static int flag_preserve_paths = 0;
|
||
|
||
/* Output the number of times a branch was taken as opposed to the percentage
|
||
of times it was taken. */
|
||
|
||
static int flag_counts = 0;
|
||
|
||
/* Return code of the tool invocation. */
|
||
static int return_code = 0;
|
||
|
||
/* Forward declarations. */
|
||
static int process_args (int, char **);
|
||
static void print_usage (int) ATTRIBUTE_NORETURN;
|
||
static void print_version (void) ATTRIBUTE_NORETURN;
|
||
static void process_file (const char *);
|
||
static void 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 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_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_info *);
|
||
static void output_lines (FILE *, const source_info *);
|
||
static string make_gcov_file_name (const char *, const char *);
|
||
static char *mangle_name (const char *);
|
||
static void release_structures (void);
|
||
extern int main (int, char **);
|
||
|
||
function_info::function_info (): m_name (NULL), m_demangled_name (NULL),
|
||
ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0),
|
||
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)
|
||
{
|
||
}
|
||
|
||
function_info::~function_info ()
|
||
{
|
||
for (int i = blocks.size () - 1; i >= 0; i--)
|
||
{
|
||
arc_info *arc, *arc_n;
|
||
|
||
for (arc = blocks[i].succ; arc; arc = arc_n)
|
||
{
|
||
arc_n = arc->succ_next;
|
||
free (arc);
|
||
}
|
||
}
|
||
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!
|
||
There are a bajillion algorithms that do this. Boost's function is named
|
||
hawick_cycles, so I used the algorithm by K. A. Hawick and H. A. James in
|
||
"Enumerating Circuits and Loops in Graphs with Self-Arcs and Multiple-Arcs"
|
||
(url at <http://complexity.massey.ac.nz/cstn/013/cstn-013.pdf>).
|
||
|
||
The basic algorithm is simple: effectively, we're finding all simple paths
|
||
in a subgraph (that shrinks every iteration). Duplicates are filtered by
|
||
"blocking" a path when a node is added to the path (this also prevents non-
|
||
simple paths)--the node is unblocked only when it participates in a cycle.
|
||
*/
|
||
|
||
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 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
|
||
that amount. */
|
||
int64_t cycle_count = INTTYPE_MAXIMUM (int64_t);
|
||
for (unsigned i = 0; i < edges.size (); i++)
|
||
{
|
||
int64_t ecount = edges[i]->cs_count;
|
||
if (cycle_count > ecount)
|
||
cycle_count = ecount;
|
||
}
|
||
count += cycle_count;
|
||
for (unsigned i = 0; i < edges.size (); i++)
|
||
edges[i]->cs_count -= cycle_count;
|
||
|
||
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_info *u, block_vector_t &blocked,
|
||
vector<block_vector_t > &block_lists)
|
||
{
|
||
block_vector_t::iterator it = find (blocked.begin (), blocked.end (), u);
|
||
if (it == blocked.end ())
|
||
return;
|
||
|
||
unsigned index = it - blocked.begin ();
|
||
blocked.erase (it);
|
||
|
||
block_vector_t to_unblock (block_lists[index]);
|
||
|
||
block_lists.erase (block_lists.begin () + index);
|
||
|
||
for (block_vector_t::iterator it = to_unblock.begin ();
|
||
it != to_unblock.end (); it++)
|
||
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 bool
|
||
circuit (block_info *v, arc_vector_t &path, block_info *start,
|
||
block_vector_t &blocked, vector<block_vector_t> &block_lists,
|
||
line_info &linfo, int64_t &count)
|
||
{
|
||
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_info *arc = v->succ; arc; arc = arc->succ_next)
|
||
{
|
||
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. */
|
||
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 (loop_found)
|
||
unblock (v, blocked, block_lists);
|
||
else
|
||
for (arc_info *arc = v->succ; arc; arc = arc->succ_next)
|
||
{
|
||
block_info *w = arc->dst;
|
||
if (w < start
|
||
|| arc->cs_count <= 0
|
||
|| !linfo.has_block (w))
|
||
continue;
|
||
|
||
size_t index
|
||
= find (blocked.begin (), blocked.end (), w) - blocked.begin ();
|
||
gcc_assert (index < blocked.size ());
|
||
block_vector_t &list = block_lists[index];
|
||
if (find (list.begin (), list.end (), v) == list.end ())
|
||
list.push_back (v);
|
||
}
|
||
|
||
return loop_found;
|
||
}
|
||
|
||
/* Find cycles for a LINFO. */
|
||
|
||
static gcov_type
|
||
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
|
||
(except for reducing counts, but that shouldn't matter anyways).
|
||
Therefore, operating on a permuted order (i.e., non-sorted) only
|
||
has the effect of permuting the output cycles. */
|
||
|
||
gcov_type count = 0;
|
||
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;
|
||
circuit (*it, path, *it, blocked, block_lists, linfo, count);
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
int
|
||
main (int argc, char **argv)
|
||
{
|
||
int argno;
|
||
int first_arg;
|
||
const char *p;
|
||
|
||
p = argv[0] + strlen (argv[0]);
|
||
while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
|
||
--p;
|
||
progname = p;
|
||
|
||
xmalloc_set_program_name (progname);
|
||
|
||
/* Unlock the stdio streams. */
|
||
unlock_std_streams ();
|
||
|
||
gcc_init_libintl ();
|
||
|
||
diagnostic_initialize (global_dc, 0);
|
||
|
||
/* Handle response files. */
|
||
expandargv (&argc, &argv);
|
||
|
||
argno = process_args (argc, argv);
|
||
if (optind == argc)
|
||
print_usage (true);
|
||
|
||
if (argc - argno > 1)
|
||
multiple_files = 1;
|
||
|
||
first_arg = argno;
|
||
|
||
for (; argno != argc; argno++)
|
||
{
|
||
if (flag_display_progress)
|
||
printf ("Processing file %d out of %d\n", argno - first_arg + 1,
|
||
argc - first_arg);
|
||
process_file (argv[argno]);
|
||
|
||
if (flag_json_format || argno == argc - 1)
|
||
{
|
||
process_all_functions ();
|
||
generate_results (argv[argno]);
|
||
release_structures ();
|
||
}
|
||
}
|
||
|
||
if (!flag_use_stdout)
|
||
executed_summary (total_lines, total_executed);
|
||
|
||
return return_code;
|
||
}
|
||
|
||
/* Print a usage message and exit. If ERROR_P is nonzero, this is an error,
|
||
otherwise the output of --help. */
|
||
|
||
static void
|
||
print_usage (int error_p)
|
||
{
|
||
FILE *file = error_p ? stderr : stdout;
|
||
int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
|
||
|
||
fnotice (file, "Usage: gcov [OPTION...] SOURCE|OBJ...\n\n");
|
||
fnotice (file, "Print code coverage information.\n\n");
|
||
fnotice (file, " -a, --all-blocks Show information for every basic block\n");
|
||
fnotice (file, " -b, --branch-probabilities Include branch probabilities in output\n");
|
||
fnotice (file, " -c, --branch-counts Output counts of branches taken\n\
|
||
rather than percentages\n");
|
||
fnotice (file, " -d, --display-progress Display progress information\n");
|
||
fnotice (file, " -D, --debug Display debugging dumps\n");
|
||
fnotice (file, " -f, --function-summaries Output summaries for each function\n");
|
||
fnotice (file, " -h, --help Print this help, then exit\n");
|
||
fnotice (file, " -j, --json-format Output JSON intermediate format\n\
|
||
into .gcov.json.gz file\n");
|
||
fnotice (file, " -H, --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, " -m, --demangled-names Output demangled function names\n");
|
||
fnotice (file, " -n, --no-output Do not create an output file\n");
|
||
fnotice (file, " -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n");
|
||
fnotice (file, " -p, --preserve-paths Preserve all pathname components\n");
|
||
fnotice (file, " -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");
|
||
fnotice (file, " -x, --hash-filenames Hash long pathnames\n");
|
||
fnotice (file, "\nObsolete options:\n");
|
||
fnotice (file, " -i, --json-format Replaced with -j, --json-format\n");
|
||
fnotice (file, " -j, --human-readable Replaced with -H, --human-readable\n");
|
||
fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
|
||
bug_report_url);
|
||
exit (status);
|
||
}
|
||
|
||
/* Print version information and exit. */
|
||
|
||
static void
|
||
print_version (void)
|
||
{
|
||
fnotice (stdout, "gcov %s%s\n", pkgversion_string, version_string);
|
||
fprintf (stdout, "Copyright %s 2022 Free Software Foundation, Inc.\n",
|
||
_("(C)"));
|
||
fnotice (stdout,
|
||
_("This is free software; see the source for copying conditions. There is NO\n\
|
||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
|
||
exit (SUCCESS_EXIT_CODE);
|
||
}
|
||
|
||
static const struct option options[] =
|
||
{
|
||
{ "help", no_argument, NULL, 'h' },
|
||
{ "version", no_argument, NULL, 'v' },
|
||
{ "verbose", no_argument, NULL, 'w' },
|
||
{ "all-blocks", no_argument, NULL, 'a' },
|
||
{ "branch-probabilities", no_argument, NULL, 'b' },
|
||
{ "branch-counts", no_argument, NULL, 'c' },
|
||
{ "json-format", no_argument, NULL, 'j' },
|
||
{ "human-readable", no_argument, NULL, 'H' },
|
||
{ "no-output", no_argument, NULL, 'n' },
|
||
{ "long-file-names", no_argument, NULL, 'l' },
|
||
{ "function-summaries", no_argument, NULL, 'f' },
|
||
{ "demangled-names", no_argument, NULL, 'm' },
|
||
{ "preserve-paths", no_argument, NULL, 'p' },
|
||
{ "relative-only", no_argument, NULL, 'r' },
|
||
{ "object-directory", required_argument, NULL, 'o' },
|
||
{ "object-file", required_argument, NULL, 'o' },
|
||
{ "source-prefix", required_argument, NULL, 's' },
|
||
{ "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' },
|
||
{ "debug", no_argument, NULL, 'D' },
|
||
{ 0, 0, 0, 0 }
|
||
};
|
||
|
||
/* Process args, return index to first non-arg. */
|
||
|
||
static int
|
||
process_args (int argc, char **argv)
|
||
{
|
||
int opt;
|
||
|
||
const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
|
||
while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
|
||
{
|
||
switch (opt)
|
||
{
|
||
case 'a':
|
||
flag_all_blocks = 1;
|
||
break;
|
||
case 'b':
|
||
flag_branches = 1;
|
||
break;
|
||
case 'c':
|
||
flag_counts = 1;
|
||
break;
|
||
case 'f':
|
||
flag_function_summary = 1;
|
||
break;
|
||
case 'h':
|
||
print_usage (false);
|
||
/* print_usage will exit. */
|
||
case 'l':
|
||
flag_long_names = 1;
|
||
break;
|
||
case 'H':
|
||
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;
|
||
case 'n':
|
||
flag_gcov_file = 0;
|
||
break;
|
||
case 'o':
|
||
object_directory = optarg;
|
||
break;
|
||
case 's':
|
||
source_prefix = optarg;
|
||
source_length = strlen (source_prefix);
|
||
break;
|
||
case 'r':
|
||
flag_relative_only = 1;
|
||
break;
|
||
case 'p':
|
||
flag_preserve_paths = 1;
|
||
break;
|
||
case 'u':
|
||
flag_unconditional = 1;
|
||
break;
|
||
case 'i':
|
||
case 'j':
|
||
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 'D':
|
||
flag_debug = 1;
|
||
break;
|
||
case 'v':
|
||
print_version ();
|
||
/* print_version will exit. */
|
||
default:
|
||
print_usage (true);
|
||
/* print_usage will exit. */
|
||
}
|
||
}
|
||
|
||
return optind;
|
||
}
|
||
|
||
/* Output intermediate LINE sitting on LINE_NUM to JSON OBJECT.
|
||
Add FUNCTION_NAME to the LINE. */
|
||
|
||
static void
|
||
output_intermediate_json_line (json::array *object,
|
||
line_info *line, unsigned line_num,
|
||
const char *function_name)
|
||
{
|
||
if (!line->exists)
|
||
return;
|
||
|
||
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);
|
||
}
|
||
|
||
/* Strip filename extension in STR. */
|
||
|
||
static string
|
||
strip_extention (string str)
|
||
{
|
||
string::size_type pos = str.rfind ('.');
|
||
if (pos != string::npos)
|
||
str = str.substr (0, pos);
|
||
|
||
return str;
|
||
}
|
||
|
||
/* Calcualte md5sum for INPUT string and return it in hex string format. */
|
||
|
||
static string
|
||
get_md5sum (const char *input)
|
||
{
|
||
md5_ctx ctx;
|
||
char md5sum[16];
|
||
string str;
|
||
|
||
md5_init_ctx (&ctx);
|
||
md5_process_bytes (input, strlen (input), &ctx);
|
||
md5_finish_ctx (&ctx, md5sum);
|
||
|
||
for (unsigned i = 0; i < 16; i++)
|
||
{
|
||
char b[3];
|
||
sprintf (b, "%02x", (unsigned char)md5sum[i]);
|
||
str += b;
|
||
}
|
||
|
||
return str;
|
||
}
|
||
|
||
/* 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 string
|
||
get_gcov_intermediate_filename (const char *input_file_name)
|
||
{
|
||
string base = basename (input_file_name);
|
||
string str = strip_extention (base);
|
||
|
||
if (flag_hash_filenames)
|
||
{
|
||
str += "##";
|
||
str += get_md5sum (input_file_name);
|
||
}
|
||
else if (flag_preserve_paths && base != input_file_name)
|
||
{
|
||
str += "##";
|
||
str += mangle_path (input_file_name);
|
||
str = strip_extention (str);
|
||
}
|
||
|
||
str += ".gcov.json.gz";
|
||
return str.c_str ();
|
||
}
|
||
|
||
/* Output the result in JSON intermediate format.
|
||
Source info SRC is dumped into JSON_FILES which is JSON array. */
|
||
|
||
static void
|
||
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);
|
||
|
||
vector<function_info *> last_non_group_fns;
|
||
|
||
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 info for all 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_fns.push_back (*it2);
|
||
|
||
vector<line_info> &lines = (*it2)->lines;
|
||
/* The LINES array is allocated only for group functions. */
|
||
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 ())
|
||
{
|
||
unsigned size = last_non_group_fns.size ();
|
||
function_info *last_fn = size > 0 ? last_non_group_fns[size - 1] : NULL;
|
||
const char *fname = last_fn ? last_fn->m_name : NULL;
|
||
output_intermediate_json_line (lineso, &src->lines[line_num], line_num,
|
||
fname);
|
||
|
||
/* Pop ending function from stack. */
|
||
if (last_fn != NULL && last_fn->end_line == line_num)
|
||
last_non_group_fns.pop_back ();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 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 const bool empty_zero_p = false;
|
||
|
||
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)
|
||
{
|
||
create_file_names (file_name);
|
||
|
||
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_info *fn = *it;
|
||
unsigned src = fn->src;
|
||
|
||
if (!fn->counts.empty () || no_data_file)
|
||
{
|
||
source_info *s = &sources[src];
|
||
s->add_function (fn);
|
||
|
||
/* Mark last line in files touched by function. */
|
||
for (unsigned block_no = 0; block_no != fn->blocks.size ();
|
||
block_no++)
|
||
{
|
||
block_info *block = &fn->blocks[block_no];
|
||
for (unsigned i = 0; i < block->locations.size (); i++)
|
||
{
|
||
/* 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 ();
|
||
|
||
/* 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);
|
||
}
|
||
else
|
||
{
|
||
/* The function was not in the executable -- some other
|
||
instance must have been selected. */
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
output_gcov_file (const char *file_name, source_info *src)
|
||
{
|
||
string gcov_file_name_str
|
||
= make_gcov_file_name (file_name, src->coverage.name);
|
||
const char *gcov_file_name = gcov_file_name_str.c_str ();
|
||
|
||
if (src->coverage.lines)
|
||
{
|
||
FILE *gcov_file = fopen (gcov_file_name, "w");
|
||
if (gcov_file)
|
||
{
|
||
fnotice (stdout, "Creating '%s'\n", gcov_file_name);
|
||
output_lines (gcov_file, src);
|
||
if (ferror (gcov_file))
|
||
{
|
||
fnotice (stderr, "Error writing output file '%s'\n",
|
||
gcov_file_name);
|
||
return_code = 6;
|
||
}
|
||
fclose (gcov_file);
|
||
}
|
||
else
|
||
{
|
||
fnotice (stderr, "Could not open output file '%s'\n", gcov_file_name);
|
||
return_code = 6;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
unlink (gcov_file_name);
|
||
fnotice (stdout, "Removing '%s'\n", gcov_file_name);
|
||
}
|
||
}
|
||
|
||
static void
|
||
generate_results (const char *file_name)
|
||
{
|
||
string gcov_intermediate_filename;
|
||
|
||
for (vector<function_info *>::iterator it = functions.begin ();
|
||
it != functions.end (); it++)
|
||
{
|
||
function_info *fn = *it;
|
||
coverage_info coverage;
|
||
|
||
memset (&coverage, 0, sizeof (coverage));
|
||
coverage.name = fn->get_name ();
|
||
add_line_counts (flag_function_summary ? &coverage : NULL, fn);
|
||
if (flag_function_summary)
|
||
{
|
||
function_summary (&coverage);
|
||
fnotice (stdout, "\n");
|
||
}
|
||
}
|
||
|
||
name_map needle;
|
||
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++)
|
||
{
|
||
source_info *src = &(*it);
|
||
if (flag_relative_only)
|
||
{
|
||
/* Ignore this source, if it is an absolute path (after
|
||
source prefix removal). */
|
||
char first = src->coverage.name[0];
|
||
|
||
#if HAVE_DOS_BASED_FILE_SYSTEM
|
||
if (first && src->coverage.name[1] == ':')
|
||
first = src->coverage.name[2];
|
||
#endif
|
||
if (IS_DIR_SEPARATOR (first))
|
||
continue;
|
||
}
|
||
|
||
accumulate_line_counts (src);
|
||
if (flag_debug)
|
||
src->debug ();
|
||
|
||
if (!flag_use_stdout)
|
||
file_summary (&src->coverage);
|
||
total_lines += src->coverage.lines;
|
||
total_executed += src->coverage.lines_executed;
|
||
if (flag_gcov_file)
|
||
{
|
||
if (flag_json_format)
|
||
{
|
||
output_json_intermediate_file (json_files, src);
|
||
if (!flag_use_stdout)
|
||
fnotice (stdout, "\n");
|
||
}
|
||
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);
|
||
|
||
fnotice (stdout, "Creating '%s'\n",
|
||
gcov_intermediate_filename.c_str ());
|
||
gzFile output = gzopen (gcov_intermediate_filename.c_str (), "w");
|
||
if (output == NULL)
|
||
{
|
||
fnotice (stderr, "Cannot open JSON output file %s\n",
|
||
gcov_intermediate_filename.c_str ());
|
||
return_code = 6;
|
||
return;
|
||
}
|
||
|
||
if (gzputs (output, pp_formatted_text (&pp)) == EOF
|
||
|| gzclose (output))
|
||
{
|
||
fnotice (stderr, "Error writing JSON output file %s\n",
|
||
gcov_intermediate_filename.c_str ());
|
||
return_code = 6;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Release all memory used. */
|
||
|
||
static void
|
||
release_structures (void)
|
||
{
|
||
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
|
||
is not specified, these are named from FILE_NAME sans extension. If
|
||
OBJECT_DIRECTORY is specified and is a directory, the files are in that
|
||
directory, but named from the basename of the FILE_NAME, sans extension.
|
||
Otherwise OBJECT_DIRECTORY is taken to be the name of the object *file*
|
||
and the data files are named from that. */
|
||
|
||
static void
|
||
create_file_names (const char *file_name)
|
||
{
|
||
char *cptr;
|
||
char *name;
|
||
int length = strlen (file_name);
|
||
int base;
|
||
|
||
/* Free previous file names. */
|
||
free (bbg_file_name);
|
||
free (da_file_name);
|
||
da_file_name = bbg_file_name = NULL;
|
||
bbg_file_time = 0;
|
||
bbg_stamp = 0;
|
||
|
||
if (object_directory && object_directory[0])
|
||
{
|
||
struct stat status;
|
||
|
||
length += strlen (object_directory) + 2;
|
||
name = XNEWVEC (char, length);
|
||
name[0] = 0;
|
||
|
||
base = !stat (object_directory, &status) && S_ISDIR (status.st_mode);
|
||
strcat (name, object_directory);
|
||
if (base && (!IS_DIR_SEPARATOR (name[strlen (name) - 1])))
|
||
strcat (name, "/");
|
||
}
|
||
else
|
||
{
|
||
name = XNEWVEC (char, length + 1);
|
||
strcpy (name, file_name);
|
||
base = 0;
|
||
}
|
||
|
||
if (base)
|
||
{
|
||
/* Append source file name. */
|
||
const char *cptr = lbasename (file_name);
|
||
strcat (name, cptr ? cptr : file_name);
|
||
}
|
||
|
||
/* Remove the extension. */
|
||
cptr = strrchr (CONST_CAST (char *, lbasename (name)), '.');
|
||
if (cptr)
|
||
*cptr = 0;
|
||
|
||
length = strlen (name);
|
||
|
||
bbg_file_name = XNEWVEC (char, length + strlen (GCOV_NOTE_SUFFIX) + 1);
|
||
strcpy (bbg_file_name, name);
|
||
strcpy (bbg_file_name + length, GCOV_NOTE_SUFFIX);
|
||
|
||
da_file_name = XNEWVEC (char, length + strlen (GCOV_DATA_SUFFIX) + 1);
|
||
strcpy (da_file_name, name);
|
||
strcpy (da_file_name + length, GCOV_DATA_SUFFIX);
|
||
|
||
free (name);
|
||
return;
|
||
}
|
||
|
||
/* Find or create a source file structure for FILE_NAME. Copies
|
||
FILE_NAME on creation */
|
||
|
||
static unsigned
|
||
find_source (const char *file_name)
|
||
{
|
||
char *canon;
|
||
unsigned idx;
|
||
struct stat status;
|
||
|
||
if (!file_name)
|
||
file_name = "<unknown>";
|
||
|
||
name_map needle;
|
||
needle.name = file_name;
|
||
|
||
vector<name_map>::iterator it = std::find (names.begin (), names.end (),
|
||
needle);
|
||
if (it != names.end ())
|
||
{
|
||
idx = it->src;
|
||
goto check_date;
|
||
}
|
||
|
||
/* Not found, try the canonical name. */
|
||
canon = canonicalize_name (file_name);
|
||
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 ();
|
||
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
|
||
prefix. */
|
||
&& !strncasecmp (source_prefix, src->coverage.name, source_length)
|
||
#else
|
||
&& !strncmp (source_prefix, src->coverage.name, source_length)
|
||
#endif
|
||
&& IS_DIR_SEPARATOR (src->coverage.name[source_length]))
|
||
src->coverage.name += source_length + 1;
|
||
if (!stat (src->name, &status))
|
||
src->file_time = status.st_mtime;
|
||
}
|
||
else
|
||
idx = it->src;
|
||
|
||
needle.name = file_name;
|
||
if (std::find (names.begin (), names.end (), needle) == names.end ())
|
||
{
|
||
/* Append the non-canonical name. */
|
||
names.push_back (name_map (xstrdup (file_name), idx));
|
||
}
|
||
|
||
/* Resort the name map. */
|
||
std::sort (names.begin (), names.end ());
|
||
|
||
check_date:
|
||
if (sources[idx].file_time > bbg_file_time)
|
||
{
|
||
static int info_emitted;
|
||
|
||
fnotice (stderr, "%s:source file is newer than notes file '%s'\n",
|
||
file_name, bbg_file_name);
|
||
if (!info_emitted)
|
||
{
|
||
fnotice (stderr,
|
||
"(the message is displayed only once per source file)\n");
|
||
info_emitted = 1;
|
||
}
|
||
sources[idx].file_time = 0;
|
||
}
|
||
|
||
return idx;
|
||
}
|
||
|
||
/* Read the notes file. Save functions to FUNCTIONS global vector. */
|
||
|
||
static void
|
||
read_graph_file (void)
|
||
{
|
||
unsigned version;
|
||
unsigned current_tag = 0;
|
||
unsigned tag;
|
||
|
||
if (!gcov_open (bbg_file_name, 1))
|
||
{
|
||
fnotice (stderr, "%s:cannot open notes file\n", bbg_file_name);
|
||
return_code = 1;
|
||
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);
|
||
return_code = 2;
|
||
gcov_close ();
|
||
return;
|
||
}
|
||
|
||
version = gcov_read_unsigned ();
|
||
if (version != GCOV_VERSION)
|
||
{
|
||
char v[4], e[4];
|
||
|
||
GCOV_UNSIGNED2STRING (v, version);
|
||
GCOV_UNSIGNED2STRING (e, GCOV_VERSION);
|
||
|
||
fnotice (stderr, "%s:version '%.4s', prefer '%.4s'\n",
|
||
bbg_file_name, v, e);
|
||
return_code = 3;
|
||
}
|
||
bbg_stamp = gcov_read_unsigned ();
|
||
/* Read checksum. */
|
||
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 ();
|
||
gcov_position_t base = gcov_position ();
|
||
|
||
if (tag == GCOV_TAG_FUNCTION)
|
||
{
|
||
char *function_name;
|
||
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 ());
|
||
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_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->start_line = start_line;
|
||
fn->start_column = start_column;
|
||
fn->end_line = end_line;
|
||
fn->end_column = end_column;
|
||
fn->artificial = artificial;
|
||
|
||
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->get_name ());
|
||
else
|
||
fn->blocks.resize (gcov_read_unsigned ());
|
||
}
|
||
else if (fn && tag == GCOV_TAG_ARCS)
|
||
{
|
||
unsigned src = gcov_read_unsigned ();
|
||
fn->blocks[src].id = src;
|
||
unsigned num_dests = GCOV_TAG_ARCS_NUM (length);
|
||
block_info *src_blk = &fn->blocks[src];
|
||
unsigned mark_catches = 0;
|
||
struct arc_info *arc;
|
||
|
||
if (src >= fn->blocks.size () || fn->blocks[src].succ)
|
||
goto corrupt;
|
||
|
||
while (num_dests--)
|
||
{
|
||
unsigned dest = gcov_read_unsigned ();
|
||
unsigned flags = gcov_read_unsigned ();
|
||
|
||
if (dest >= fn->blocks.size ())
|
||
goto corrupt;
|
||
arc = XCNEW (arc_info);
|
||
|
||
arc->dst = &fn->blocks[dest];
|
||
/* Set id in order to find EXIT_BLOCK. */
|
||
arc->dst->id = dest;
|
||
arc->src = src_blk;
|
||
|
||
arc->count = 0;
|
||
arc->count_valid = 0;
|
||
arc->on_tree = !!(flags & GCOV_ARC_ON_TREE);
|
||
arc->fake = !!(flags & GCOV_ARC_FAKE);
|
||
arc->fall_through = !!(flags & GCOV_ARC_FALLTHROUGH);
|
||
|
||
arc->succ_next = src_blk->succ;
|
||
src_blk->succ = arc;
|
||
src_blk->num_succ++;
|
||
|
||
arc->pred_next = fn->blocks[dest].pred;
|
||
fn->blocks[dest].pred = arc;
|
||
fn->blocks[dest].num_pred++;
|
||
|
||
if (arc->fake)
|
||
{
|
||
if (src)
|
||
{
|
||
/* Exceptional exit from this function, the
|
||
source block must be a call. */
|
||
fn->blocks[src].is_call_site = 1;
|
||
arc->is_call_non_return = 1;
|
||
mark_catches = 1;
|
||
}
|
||
else
|
||
{
|
||
/* Non-local return from a callee of this
|
||
function. The destination block is a setjmp. */
|
||
arc->is_nonlocal_return = 1;
|
||
fn->blocks[dest].is_nonlocal_return = 1;
|
||
}
|
||
}
|
||
|
||
if (!arc->on_tree)
|
||
fn->counts.push_back (0);
|
||
}
|
||
|
||
if (mark_catches)
|
||
{
|
||
/* We have a fake exit from this block. The other
|
||
non-fall through exits must be to catch handlers.
|
||
Mark them as catch arcs. */
|
||
|
||
for (arc = src_blk->succ; arc; arc = arc->succ_next)
|
||
if (!arc->fake && !arc->fall_through)
|
||
{
|
||
arc->is_throw = 1;
|
||
fn->has_catch = 1;
|
||
}
|
||
}
|
||
}
|
||
else if (fn && tag == GCOV_TAG_LINES)
|
||
{
|
||
unsigned blockno = gcov_read_unsigned ();
|
||
block_info *block = &fn->blocks[blockno];
|
||
|
||
if (blockno >= fn->blocks.size ())
|
||
goto corrupt;
|
||
|
||
while (true)
|
||
{
|
||
unsigned lineno = gcov_read_unsigned ();
|
||
|
||
if (lineno)
|
||
block->locations.back ().lines.push_back (lineno);
|
||
else
|
||
{
|
||
const char *file_name = gcov_read_string ();
|
||
|
||
if (!file_name)
|
||
break;
|
||
block->locations.push_back (block_location_info
|
||
(find_source (file_name)));
|
||
}
|
||
}
|
||
}
|
||
else if (current_tag && !GCOV_TAG_IS_SUBTAG (current_tag, tag))
|
||
{
|
||
fn = NULL;
|
||
current_tag = 0;
|
||
}
|
||
gcov_sync (base, length);
|
||
if (gcov_is_error ())
|
||
{
|
||
corrupt:;
|
||
fnotice (stderr, "%s:corrupted\n", bbg_file_name);
|
||
return_code = 4;
|
||
break;
|
||
}
|
||
}
|
||
gcov_close ();
|
||
|
||
if (functions.empty ())
|
||
fnotice (stderr, "%s:no functions found\n", bbg_file_name);
|
||
}
|
||
|
||
/* Reads profiles from the count file and attach to each
|
||
function. Return nonzero if fatal error. */
|
||
|
||
static int
|
||
read_count_file (void)
|
||
{
|
||
unsigned ix;
|
||
unsigned version;
|
||
unsigned tag;
|
||
function_info *fn = NULL;
|
||
int error = 0;
|
||
map<unsigned, function_info *>::iterator it;
|
||
|
||
if (!gcov_open (da_file_name, 1))
|
||
{
|
||
fnotice (stderr, "%s:cannot open data file, assuming not executed\n",
|
||
da_file_name);
|
||
no_data_file = 1;
|
||
return 0;
|
||
}
|
||
if (!gcov_magic (gcov_read_unsigned (), GCOV_DATA_MAGIC))
|
||
{
|
||
fnotice (stderr, "%s:not a gcov data file\n", da_file_name);
|
||
return_code = 2;
|
||
cleanup:;
|
||
gcov_close ();
|
||
return 1;
|
||
}
|
||
version = gcov_read_unsigned ();
|
||
if (version != GCOV_VERSION)
|
||
{
|
||
char v[4], e[4];
|
||
|
||
GCOV_UNSIGNED2STRING (v, version);
|
||
GCOV_UNSIGNED2STRING (e, GCOV_VERSION);
|
||
|
||
fnotice (stderr, "%s:version '%.4s', prefer version '%.4s'\n",
|
||
da_file_name, v, e);
|
||
return_code = 3;
|
||
}
|
||
tag = gcov_read_unsigned ();
|
||
if (tag != bbg_stamp)
|
||
{
|
||
fnotice (stderr, "%s:stamp mismatch with notes file\n", da_file_name);
|
||
return_code = 5;
|
||
goto cleanup;
|
||
}
|
||
|
||
/* Read checksum. */
|
||
gcov_read_unsigned ();
|
||
|
||
while ((tag = gcov_read_unsigned ()))
|
||
{
|
||
unsigned length = gcov_read_unsigned ();
|
||
int read_length = (int)length;
|
||
unsigned long base = gcov_position ();
|
||
|
||
if (tag == GCOV_TAG_OBJECT_SUMMARY)
|
||
{
|
||
struct gcov_summary summary;
|
||
gcov_read_summary (&summary);
|
||
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;
|
||
ident = gcov_read_unsigned ();
|
||
fn = NULL;
|
||
it = ident_to_fn.find (ident);
|
||
if (it != ident_to_fn.end ())
|
||
fn = it->second;
|
||
|
||
if (!fn)
|
||
;
|
||
else if (gcov_read_unsigned () != fn->lineno_checksum
|
||
|| gcov_read_unsigned () != fn->cfg_checksum)
|
||
{
|
||
mismatch:;
|
||
fnotice (stderr, "%s:profile mismatch for '%s'\n",
|
||
da_file_name, fn->get_name ());
|
||
goto cleanup;
|
||
}
|
||
}
|
||
else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
|
||
{
|
||
length = abs (read_length);
|
||
if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
|
||
goto mismatch;
|
||
|
||
if (read_length > 0)
|
||
for (ix = 0; ix != fn->counts.size (); ix++)
|
||
fn->counts[ix] += gcov_read_counter ();
|
||
}
|
||
if (read_length < 0)
|
||
read_length = 0;
|
||
gcov_sync (base, read_length);
|
||
if ((error = gcov_is_error ()))
|
||
{
|
||
fnotice (stderr,
|
||
error < 0
|
||
? N_("%s:overflowed\n")
|
||
: N_("%s:corrupted\n"),
|
||
da_file_name);
|
||
return_code = 4;
|
||
goto cleanup;
|
||
}
|
||
}
|
||
|
||
gcov_close ();
|
||
return 0;
|
||
}
|
||
|
||
/* Solve the flow graph. Propagate counts from the instrumented arcs
|
||
to the blocks and the uninstrumented arcs. */
|
||
|
||
static void
|
||
solve_flow_graph (function_info *fn)
|
||
{
|
||
unsigned ix;
|
||
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_info *arc_p, *arc_n;
|
||
|
||
for (arc_p = NULL, arc = fn->blocks[ix].succ; arc;
|
||
arc_p = arc, arc = arc_n)
|
||
{
|
||
arc_n = arc->succ_next;
|
||
arc->succ_next = arc_p;
|
||
}
|
||
fn->blocks[ix].succ = arc_p;
|
||
|
||
for (arc_p = NULL, arc = fn->blocks[ix].pred; arc;
|
||
arc_p = arc, arc = arc_n)
|
||
{
|
||
arc_n = arc->pred_next;
|
||
arc->pred_next = arc_p;
|
||
}
|
||
fn->blocks[ix].pred = arc_p;
|
||
}
|
||
|
||
if (fn->blocks.size () < 2)
|
||
fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n",
|
||
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->get_name ());
|
||
else
|
||
/* We can't deduce the entry block counts from the lack of
|
||
predecessors. */
|
||
fn->blocks[ENTRY_BLOCK].num_pred = ~(unsigned)0;
|
||
|
||
if (fn->blocks[EXIT_BLOCK].num_succ)
|
||
fnotice (stderr, "%s:'%s' has arcs from exit block\n",
|
||
bbg_file_name, fn->get_name ());
|
||
else
|
||
/* Likewise, we can't deduce exit block counts from the lack
|
||
of its successors. */
|
||
fn->blocks[EXIT_BLOCK].num_succ = ~(unsigned)0;
|
||
}
|
||
|
||
/* Propagate the measured counts, this must be done in the same
|
||
order as the code in profile.cc */
|
||
for (unsigned i = 0; i < fn->blocks.size (); i++)
|
||
{
|
||
blk = &fn->blocks[i];
|
||
block_info const *prev_dst = NULL;
|
||
int out_of_order = 0;
|
||
int non_fake_succ = 0;
|
||
|
||
for (arc = blk->succ; arc; arc = arc->succ_next)
|
||
{
|
||
if (!arc->fake)
|
||
non_fake_succ++;
|
||
|
||
if (!arc->on_tree)
|
||
{
|
||
if (count_ptr)
|
||
arc->count = *count_ptr++;
|
||
arc->count_valid = 1;
|
||
blk->num_succ--;
|
||
arc->dst->num_pred--;
|
||
}
|
||
if (prev_dst && prev_dst > arc->dst)
|
||
out_of_order = 1;
|
||
prev_dst = arc->dst;
|
||
}
|
||
if (non_fake_succ == 1)
|
||
{
|
||
/* If there is only one non-fake exit, it is an
|
||
unconditional branch. */
|
||
for (arc = blk->succ; arc; arc = arc->succ_next)
|
||
if (!arc->fake)
|
||
{
|
||
arc->is_unconditional = 1;
|
||
/* If this block is instrumenting a call, it might be
|
||
an artificial block. It is not artificial if it has
|
||
a non-fallthrough exit, or the destination of this
|
||
arc has more than one entry. Mark the destination
|
||
block as a return site, if none of those conditions
|
||
hold. */
|
||
if (blk->is_call_site && arc->fall_through
|
||
&& arc->dst->pred == arc && !arc->pred_next)
|
||
arc->dst->is_call_return = 1;
|
||
}
|
||
}
|
||
|
||
/* Sort the successor arcs into ascending dst order. profile.cc
|
||
normally produces arcs in the right order, but sometimes with
|
||
one or two out of order. We're not using a particularly
|
||
smart sort. */
|
||
if (out_of_order)
|
||
{
|
||
arc_info *start = blk->succ;
|
||
unsigned changes = 1;
|
||
|
||
while (changes)
|
||
{
|
||
arc_info *arc, *arc_p, *arc_n;
|
||
|
||
changes = 0;
|
||
for (arc_p = NULL, arc = start; (arc_n = arc->succ_next);)
|
||
{
|
||
if (arc->dst > arc_n->dst)
|
||
{
|
||
changes = 1;
|
||
if (arc_p)
|
||
arc_p->succ_next = arc_n;
|
||
else
|
||
start = arc_n;
|
||
arc->succ_next = arc_n->succ_next;
|
||
arc_n->succ_next = arc;
|
||
arc_p = arc_n;
|
||
}
|
||
else
|
||
{
|
||
arc_p = arc;
|
||
arc = arc_n;
|
||
}
|
||
}
|
||
}
|
||
blk->succ = start;
|
||
}
|
||
|
||
/* Place it on the invalid chain, it will be ignored if that's
|
||
wrong. */
|
||
blk->invalid_chain = 1;
|
||
blk->chain = invalid_blocks;
|
||
invalid_blocks = blk;
|
||
}
|
||
|
||
while (invalid_blocks || valid_blocks)
|
||
{
|
||
while ((blk = invalid_blocks))
|
||
{
|
||
gcov_type total = 0;
|
||
const arc_info *arc;
|
||
|
||
invalid_blocks = blk->chain;
|
||
blk->invalid_chain = 0;
|
||
if (!blk->num_succ)
|
||
for (arc = blk->succ; arc; arc = arc->succ_next)
|
||
total += arc->count;
|
||
else if (!blk->num_pred)
|
||
for (arc = blk->pred; arc; arc = arc->pred_next)
|
||
total += arc->count;
|
||
else
|
||
continue;
|
||
|
||
blk->count = total;
|
||
blk->count_valid = 1;
|
||
blk->chain = valid_blocks;
|
||
blk->valid_chain = 1;
|
||
valid_blocks = blk;
|
||
}
|
||
while ((blk = valid_blocks))
|
||
{
|
||
gcov_type total;
|
||
arc_info *arc, *inv_arc;
|
||
|
||
valid_blocks = blk->chain;
|
||
blk->valid_chain = 0;
|
||
if (blk->num_succ == 1)
|
||
{
|
||
block_info *dst;
|
||
|
||
total = blk->count;
|
||
inv_arc = NULL;
|
||
for (arc = blk->succ; arc; arc = arc->succ_next)
|
||
{
|
||
total -= arc->count;
|
||
if (!arc->count_valid)
|
||
inv_arc = arc;
|
||
}
|
||
dst = inv_arc->dst;
|
||
inv_arc->count_valid = 1;
|
||
inv_arc->count = total;
|
||
blk->num_succ--;
|
||
dst->num_pred--;
|
||
if (dst->count_valid)
|
||
{
|
||
if (dst->num_pred == 1 && !dst->valid_chain)
|
||
{
|
||
dst->chain = valid_blocks;
|
||
dst->valid_chain = 1;
|
||
valid_blocks = dst;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!dst->num_pred && !dst->invalid_chain)
|
||
{
|
||
dst->chain = invalid_blocks;
|
||
dst->invalid_chain = 1;
|
||
invalid_blocks = dst;
|
||
}
|
||
}
|
||
}
|
||
if (blk->num_pred == 1)
|
||
{
|
||
block_info *src;
|
||
|
||
total = blk->count;
|
||
inv_arc = NULL;
|
||
for (arc = blk->pred; arc; arc = arc->pred_next)
|
||
{
|
||
total -= arc->count;
|
||
if (!arc->count_valid)
|
||
inv_arc = arc;
|
||
}
|
||
src = inv_arc->src;
|
||
inv_arc->count_valid = 1;
|
||
inv_arc->count = total;
|
||
blk->num_pred--;
|
||
src->num_succ--;
|
||
if (src->count_valid)
|
||
{
|
||
if (src->num_succ == 1 && !src->valid_chain)
|
||
{
|
||
src->chain = valid_blocks;
|
||
src->valid_chain = 1;
|
||
valid_blocks = src;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!src->num_succ && !src->invalid_chain)
|
||
{
|
||
src->chain = invalid_blocks;
|
||
src->invalid_chain = 1;
|
||
invalid_blocks = src;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If the graph has been correctly solved, every block will have a
|
||
valid count. */
|
||
for (unsigned i = 0; ix < fn->blocks.size (); i++)
|
||
if (!fn->blocks[i].count_valid)
|
||
{
|
||
fnotice (stderr, "%s:graph is unsolvable for '%s'\n",
|
||
bbg_file_name, fn->get_name ());
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Mark all the blocks only reachable via an incoming catch. */
|
||
|
||
static void
|
||
find_exception_blocks (function_info *fn)
|
||
{
|
||
unsigned ix;
|
||
block_info **queue = XALLOCAVEC (block_info *, fn->blocks.size ());
|
||
|
||
/* First mark all blocks as exceptional. */
|
||
for (ix = fn->blocks.size (); ix--;)
|
||
fn->blocks[ix].exceptional = 1;
|
||
|
||
/* Now mark all the blocks reachable via non-fake edges */
|
||
queue[0] = &fn->blocks[0];
|
||
queue[0]->exceptional = 0;
|
||
for (ix = 1; ix;)
|
||
{
|
||
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)
|
||
{
|
||
arc->dst->exceptional = 0;
|
||
queue[ix++] = arc->dst;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Increment totals in COVERAGE according to arc ARC. */
|
||
|
||
static void
|
||
add_branch_counts (coverage_info *coverage, const arc_info *arc)
|
||
{
|
||
if (arc->is_call_non_return)
|
||
{
|
||
coverage->calls++;
|
||
if (arc->src->count)
|
||
coverage->calls_executed++;
|
||
}
|
||
else if (!arc->is_unconditional)
|
||
{
|
||
coverage->branches++;
|
||
if (arc->src->count)
|
||
coverage->branches_executed++;
|
||
if (arc->count)
|
||
coverage->branches_taken++;
|
||
}
|
||
}
|
||
|
||
/* Format COUNT, if flag_human_readable_numbers is set, return it human
|
||
readable format. */
|
||
|
||
static char const *
|
||
format_count (gcov_type count)
|
||
{
|
||
static char buffer[64];
|
||
const char *units = " kMGTPEZY";
|
||
|
||
if (count < 1000 || !flag_human_readable_numbers)
|
||
{
|
||
sprintf (buffer, "%" PRId64, count);
|
||
return buffer;
|
||
}
|
||
|
||
unsigned i;
|
||
gcov_type divisor = 1;
|
||
for (i = 0; units[i+1]; i++, divisor *= 1000)
|
||
{
|
||
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
|
||
return format_count (top);
|
||
|
||
return buffer;
|
||
}
|
||
|
||
/* Summary of execution */
|
||
|
||
static void
|
||
executed_summary (unsigned lines, unsigned executed)
|
||
{
|
||
if (lines)
|
||
fnotice (stdout, "Lines executed:%s of %d\n",
|
||
format_gcov (executed, lines, 2), lines);
|
||
else
|
||
fnotice (stdout, "No executable lines\n");
|
||
}
|
||
|
||
/* Output summary info for a function. */
|
||
|
||
static void
|
||
function_summary (const coverage_info *coverage)
|
||
{
|
||
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)
|
||
{
|
||
if (coverage->branches)
|
||
{
|
||
fnotice (stdout, "Branches executed:%s of %d\n",
|
||
format_gcov (coverage->branches_executed,
|
||
coverage->branches, 2),
|
||
coverage->branches);
|
||
fnotice (stdout, "Taken at least once:%s of %d\n",
|
||
format_gcov (coverage->branches_taken,
|
||
coverage->branches, 2),
|
||
coverage->branches);
|
||
}
|
||
else
|
||
fnotice (stdout, "No branches\n");
|
||
if (coverage->calls)
|
||
fnotice (stdout, "Calls executed:%s of %d\n",
|
||
format_gcov (coverage->calls_executed, coverage->calls, 2),
|
||
coverage->calls);
|
||
else
|
||
fnotice (stdout, "No calls\n");
|
||
}
|
||
}
|
||
|
||
/* Canonicalize the filename NAME by canonicalizing directory
|
||
separators, eliding . components and resolving .. components
|
||
appropriately. Always returns a unique string. */
|
||
|
||
static char *
|
||
canonicalize_name (const char *name)
|
||
{
|
||
/* The canonical name cannot be longer than the incoming name. */
|
||
char *result = XNEWVEC (char, strlen (name) + 1);
|
||
const char *base = name, *probe;
|
||
char *ptr = result;
|
||
char *dd_base;
|
||
int slash = 0;
|
||
|
||
#if HAVE_DOS_BASED_FILE_SYSTEM
|
||
if (base[0] && base[1] == ':')
|
||
{
|
||
result[0] = base[0];
|
||
result[1] = ':';
|
||
base += 2;
|
||
ptr += 2;
|
||
}
|
||
#endif
|
||
for (dd_base = ptr; *base; base = probe)
|
||
{
|
||
size_t len;
|
||
|
||
for (probe = base; *probe; probe++)
|
||
if (IS_DIR_SEPARATOR (*probe))
|
||
break;
|
||
|
||
len = probe - base;
|
||
if (len == 1 && base[0] == '.')
|
||
/* Elide a '.' directory */
|
||
;
|
||
else if (len == 2 && base[0] == '.' && base[1] == '.')
|
||
{
|
||
/* '..', we can only elide it and the previous directory, if
|
||
we're not a symlink. */
|
||
struct stat ATTRIBUTE_UNUSED buf;
|
||
|
||
*ptr = 0;
|
||
if (dd_base == ptr
|
||
#if defined (S_ISLNK)
|
||
/* S_ISLNK is not POSIX.1-1996. */
|
||
|| stat (result, &buf) || S_ISLNK (buf.st_mode)
|
||
#endif
|
||
)
|
||
{
|
||
/* Cannot elide, or unreadable or a symlink. */
|
||
dd_base = ptr + 2 + slash;
|
||
goto regular;
|
||
}
|
||
while (ptr != dd_base && *ptr != '/')
|
||
ptr--;
|
||
slash = ptr != result;
|
||
}
|
||
else
|
||
{
|
||
regular:
|
||
/* Regular pathname component. */
|
||
if (slash)
|
||
*ptr++ = '/';
|
||
memcpy (ptr, base, len);
|
||
ptr += len;
|
||
slash = 1;
|
||
}
|
||
|
||
for (; IS_DIR_SEPARATOR (*probe); probe++)
|
||
continue;
|
||
}
|
||
*ptr = 0;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* Generate an output file name. INPUT_NAME is the canonicalized main
|
||
input file and SRC_NAME is the canonicalized file name.
|
||
LONG_OUTPUT_NAMES and PRESERVE_PATHS affect name generation. With
|
||
long_output_names we prepend the processed name of the input file
|
||
to each output name (except when the current source file is the
|
||
input file, so you don't get a double concatenation). The two
|
||
components are separated by '##'. With preserve_paths we create a
|
||
filename from all path components of the source file, replacing '/'
|
||
with '#', and .. with '^', without it we simply take the basename
|
||
component. (Remember, the canonicalized name will already have
|
||
elided '.' components and converted \\ separators.) */
|
||
|
||
static string
|
||
make_gcov_file_name (const char *input_name, const char *src_name)
|
||
{
|
||
string str;
|
||
|
||
/* When hashing filenames, we shorten them by only using the filename
|
||
component and appending a hash of the full (mangled) pathname. */
|
||
if (flag_hash_filenames)
|
||
str = (string (mangle_name (src_name)) + "##"
|
||
+ get_md5sum (src_name) + ".gcov");
|
||
else
|
||
{
|
||
if (flag_long_names && input_name && strcmp (src_name, input_name) != 0)
|
||
{
|
||
str += mangle_name (input_name);
|
||
str += "##";
|
||
}
|
||
|
||
str += mangle_name (src_name);
|
||
str += ".gcov";
|
||
}
|
||
|
||
return str;
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
/* Generate the source filename part. */
|
||
if (!flag_preserve_paths)
|
||
return xstrdup (lbasename (base));
|
||
else
|
||
return mangle_path (base);
|
||
}
|
||
|
||
/* Scan through the bb_data for each line in the block, increment
|
||
the line number execution count indicated by the execution count of
|
||
the appropriate basic block. */
|
||
|
||
static void
|
||
add_line_counts (coverage_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_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++)
|
||
{
|
||
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++)
|
||
{
|
||
unsigned ln = lines[j];
|
||
|
||
/* Line belongs to a function that is in a group. */
|
||
if (fn->group_line_p (ln, src_idx))
|
||
{
|
||
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;
|
||
}
|
||
else
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
has_any_line = true;
|
||
|
||
if (!ix || ix + 1 == fn->blocks.size ())
|
||
/* Entry or exit block. */;
|
||
else if (line != NULL)
|
||
{
|
||
line->blocks.push_back (block);
|
||
|
||
if (flag_branches)
|
||
{
|
||
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->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)
|
||
{
|
||
/* First work on group functions. */
|
||
for (vector<function_info *>::iterator it = src->functions.begin ();
|
||
it != src->functions.end (); it++)
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
|
||
/* 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);
|
||
|
||
/* 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;
|
||
|
||
if (fn->src != src->index || !fn->is_group)
|
||
continue;
|
||
|
||
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 (!src_line->exists)
|
||
src->coverage.lines++;
|
||
if (!src_line->count && fn_line->count)
|
||
src->coverage.lines_executed++;
|
||
|
||
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_info *arc)
|
||
{
|
||
if (arc->is_call_non_return)
|
||
{
|
||
if (arc->src->count)
|
||
{
|
||
fnotice (gcov_file, "call %2d returned %s\n", ix,
|
||
format_gcov (arc->src->count - arc->count,
|
||
arc->src->count, -flag_counts));
|
||
}
|
||
else
|
||
fnotice (gcov_file, "call %2d never executed\n", ix);
|
||
}
|
||
else if (!arc->is_unconditional)
|
||
{
|
||
if (arc->src->count)
|
||
fnotice (gcov_file, "branch %2d taken %s%s", ix,
|
||
format_gcov (arc->count, arc->src->count, -flag_counts),
|
||
arc->fall_through ? " (fallthrough)"
|
||
: arc->is_throw ? " (throw)" : "");
|
||
else
|
||
fnotice (gcov_file, "branch %2d never executed", ix);
|
||
|
||
if (flag_verbose)
|
||
fnotice (gcov_file, " (BB %d)", arc->dst->id);
|
||
|
||
fnotice (gcov_file, "\n");
|
||
}
|
||
else if (flag_unconditional && !arc->dst->is_call_return)
|
||
{
|
||
if (arc->src->count)
|
||
fnotice (gcov_file, "unconditional %2d taken %s\n", ix,
|
||
format_gcov (arc->count, arc->src->count, -flag_counts));
|
||
else
|
||
fnotice (gcov_file, "unconditional %2d never executed\n", ix);
|
||
}
|
||
else
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
static const char *
|
||
read_line (FILE *file)
|
||
{
|
||
static char *string;
|
||
static size_t string_len;
|
||
size_t pos = 0;
|
||
|
||
if (!string_len)
|
||
{
|
||
string_len = 200;
|
||
string = XNEWVEC (char, string_len);
|
||
}
|
||
|
||
while (fgets (string + pos, string_len - pos, file))
|
||
{
|
||
size_t len = strlen (string + pos);
|
||
|
||
if (len && string[pos + len - 1] == '\n')
|
||
{
|
||
string[pos + len - 1] = 0;
|
||
return string;
|
||
}
|
||
pos += len;
|
||
/* If the file contains NUL characters or an incomplete
|
||
last line, which can happen more than once in one run,
|
||
we have to avoid doubling the STRING_LEN unnecessarily. */
|
||
if (pos > string_len / 2)
|
||
{
|
||
string_len *= 2;
|
||
string = XRESIZEVEC (char, string, string_len);
|
||
}
|
||
}
|
||
|
||
return pos ? string : NULL;
|
||
}
|
||
|
||
/* Pad string S with spaces from left to have total width equal to 9. */
|
||
|
||
static void
|
||
pad_count_string (string &s)
|
||
{
|
||
if (s.size () < 9)
|
||
s.insert (0, 9 - s.size (), ' ');
|
||
}
|
||
|
||
/* Print GCOV line beginning to F stream. If EXISTS is set to true, the
|
||
line exists in source file. UNEXCEPTIONAL indicated that it's not in
|
||
an exceptional statement. The output is printed for LINE_NUM of given
|
||
COUNT of executions. EXCEPTIONAL_STRING and UNEXCEPTIONAL_STRING are
|
||
used to indicate non-executed blocks. */
|
||
|
||
static void
|
||
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,
|
||
unsigned int maximum_count)
|
||
{
|
||
string s;
|
||
if (exists)
|
||
{
|
||
if (count > 0)
|
||
{
|
||
s = format_gcov (count, 0, -1);
|
||
if (has_unexecuted_block
|
||
&& bbg_supports_has_unexecuted_blocks)
|
||
{
|
||
if (flag_use_colors)
|
||
{
|
||
pad_count_string (s);
|
||
s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA
|
||
COLOR_SEPARATOR COLOR_FG_WHITE));
|
||
s += SGR_RESET;
|
||
}
|
||
else
|
||
s += "*";
|
||
}
|
||
pad_count_string (s);
|
||
}
|
||
else
|
||
{
|
||
if (flag_use_colors)
|
||
{
|
||
s = "0";
|
||
pad_count_string (s);
|
||
if (unexceptional)
|
||
s.insert (0, SGR_SEQ (COLOR_BG_RED
|
||
COLOR_SEPARATOR COLOR_FG_WHITE));
|
||
else
|
||
s.insert (0, SGR_SEQ (COLOR_BG_CYAN
|
||
COLOR_SEPARATOR COLOR_FG_WHITE));
|
||
s += SGR_RESET;
|
||
}
|
||
else
|
||
{
|
||
s = unexceptional ? unexceptional_string : exceptional_string;
|
||
pad_count_string (s);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
s = "-";
|
||
pad_count_string (s);
|
||
}
|
||
|
||
/* 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
|
||
the gcov file preceded by its execution count and other
|
||
information. */
|
||
|
||
static void
|
||
output_lines (FILE *gcov_file, const source_info *src)
|
||
{
|
||
#define DEFAULT_LINE_START " -: 0:"
|
||
#define FN_SEPARATOR "------------------\n"
|
||
|
||
FILE *source_file;
|
||
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)
|
||
{
|
||
fprintf (gcov_file, DEFAULT_LINE_START "Graph:%s\n", bbg_file_name);
|
||
fprintf (gcov_file, DEFAULT_LINE_START "Data:%s\n",
|
||
no_data_file ? "-" : da_file_name);
|
||
fprintf (gcov_file, DEFAULT_LINE_START "Runs:%u\n", object_runs);
|
||
}
|
||
|
||
source_file = fopen (src->name, "r");
|
||
if (!source_file)
|
||
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");
|
||
|
||
vector<const char *> source_lines;
|
||
if (source_file)
|
||
while ((retval = read_line (source_file)) != NULL)
|
||
source_lines.push_back (xstrdup (retval));
|
||
|
||
unsigned line_start_group = 0;
|
||
vector<function_info *> *fns;
|
||
|
||
for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++)
|
||
{
|
||
if (line_num >= src->lines.size ())
|
||
{
|
||
fprintf (gcov_file, "%9s:%5u", "-", line_num);
|
||
print_source_line (gcov_file, source_lines, line_num);
|
||
continue;
|
||
}
|
||
|
||
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
|
||
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, line->unexceptional,
|
||
line->has_unexecuted_block, line->count,
|
||
line_num, "=====", "#####", src->maximum_count);
|
||
|
||
print_source_line (gcov_file, source_lines, line_num);
|
||
output_line_details (gcov_file, line, line_num);
|
||
|
||
if (line_start_group == line_num)
|
||
{
|
||
for (vector<function_info *>::iterator it = fns->begin ();
|
||
it != fns->end (); it++)
|
||
{
|
||
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,
|
||
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);
|
||
}
|
||
}
|
||
|
||
fprintf (gcov_file, FN_SEPARATOR);
|
||
line_start_group = 0;
|
||
}
|
||
}
|
||
|
||
if (source_file)
|
||
fclose (source_file);
|
||
}
|