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