gcov.c (struct name_map): New.

* gcov.c (struct name_map): New.
	(names, n_names, a_names): New global vars.
	(print_usage): Adjust usage.
	(generate_results): Canonicalize main file name.
	(release_structures): Adjust.
	(name_search, name_sort): New callbacks.
	(find_source): Look for and create a canonical name.
	(canonicalize_name): New.
	(make_gcov_file_name): Reimplement and fix mangling.
	(mangle_name): New.
	* doc/gcov.texi: Update documentation about path preservation.

	testsuite/
	* gcc.misc-tests/gcov-15.c: New.

From-SVN: r181309
This commit is contained in:
Nathan Sidwell 2011-11-12 11:35:42 +00:00 committed by Nathan Sidwell
parent 926706f828
commit eeabee0aac
5 changed files with 328 additions and 110 deletions

View File

@ -1,3 +1,17 @@
2011-11-12 Nathan Sidwell <nathan@acm.org>
* gcov.c (struct name_map): New.
(names, n_names, a_names): New global vars.
(print_usage): Adjust usage.
(generate_results): Canonicalize main file name.
(release_structures): Adjust.
(name_search, name_sort): New callbacks.
(find_source): Look for and create a canonical name.
(canonicalize_name): New.
(make_gcov_file_name): Reimplement and fix mangling.
(mangle_name): New.
* doc/gcov.texi: Update documentation about path preservation.
2011-11-11 David S. Miller <davem@davemloft.net>
* config/sparc/linux.h (ASM_GENERATE_INTERNAL_LABEL): Delete.

View File

@ -113,7 +113,7 @@ compatible with any other profiling or test coverage mechanism.
@section Invoking @command{gcov}
@smallexample
gcov @r{[}@var{options}@r{]} @var{sourcefiles}
gcov @r{[}@var{options}@r{]} @var{files}
@end smallexample
@command{gcov} accepts the following options:
@ -176,11 +176,12 @@ Do not create the @command{gcov} output file.
@itemx --long-file-names
Create long file names for included source files. For example, if the
header file @file{x.h} contains code, and was included in the file
@file{a.c}, then running @command{gcov} on the file @file{a.c} will produce
an output file called @file{a.c##x.h.gcov} instead of @file{x.h.gcov}.
This can be useful if @file{x.h} is included in multiple source
files. If you use the @samp{-p} option, both the including and
included file names will be complete path names.
@file{a.c}, then running @command{gcov} on the file @file{a.c} will
produce an output file called @file{a.c##x.h.gcov} instead of
@file{x.h.gcov}. This can be useful if @file{x.h} is included in
multiple source files and you want to see the individual
contributions. If you use the @samp{-p} option, both the including
and included file names will be complete path names.
@item -p
@itemx --preserve-paths
@ -188,9 +189,9 @@ Preserve complete path information in the names of generated
@file{.gcov} files. Without this option, just the filename component is
used. With this option, all directories are used, with @samp{/} characters
translated to @samp{#} characters, @file{.} directory components
removed and @file{..}
removed and unremoveable @file{..}
components renamed to @samp{^}. This is useful if sourcefiles are in several
different directories. It also affects the @samp{-l} option.
different directories.
@item -f
@itemx --function-summaries
@ -203,9 +204,8 @@ Specify either the directory containing the gcov data files, or the
object path name. The @file{.gcno}, and
@file{.gcda} data files are searched for using this option. If a directory
is specified, the data files are in that directory and named after the
source file name, without its extension. If a file is specified here,
the data files are named after that file, without its extension. If this
option is not supplied, it defaults to the current directory.
input file name, without its extension. If a file is specified here,
the data files are named after that file, without its extension.
@item -u
@itemx --unconditional-branches
@ -223,12 +223,17 @@ when you invoked the compiler. Otherwise it will not be able to locate
the source files. @command{gcov} produces files called
@file{@var{mangledname}.gcov} in the current directory. These contain
the coverage information of the source file they correspond to.
One @file{.gcov} file is produced for each source file containing code,
One @file{.gcov} file is produced for each source (or header) file
containing code,
which was compiled to produce the data files. The @var{mangledname} part
of the output file name is usually simply the source file name, but can
be something more complicated if the @samp{-l} or @samp{-p} options are
given. Refer to those options for details.
If you invoke @command{gcov} with multiple input files, the
contributions from each input file are summed. Typically you would
invoke it with the same list of files as the final link of your executable.
The @file{.gcov} files contain the @samp{:} separated fields along with
program source code. The format is

View File

@ -231,7 +231,7 @@ typedef struct line_info
typedef struct source_info
{
/* Name of source file. */
/* Canonical name of source file. */
char *name;
time_t file_time;
@ -246,6 +246,12 @@ typedef struct source_info
function_t *functions;
} source_t;
typedef struct name_map
{
char *name; /* Source file name */
unsigned src; /* Source file */
} name_map_t;
/* Holds a list of function basic block graphs. */
static function_t *functions;
@ -255,6 +261,10 @@ static source_t *sources; /* Array of source files */
static unsigned n_sources; /* Number of sources */
static unsigned a_sources; /* Allocated sources */
static name_map_t *names; /* Mapping of file names to sources */
static unsigned n_names; /* Number of names */
static unsigned a_names; /* Allocated names */
/* This holds data summary information. */
static unsigned object_runs;
@ -341,6 +351,9 @@ static void print_version (void) ATTRIBUTE_NORETURN;
static void process_file (const char *);
static void generate_results (const char *);
static void create_file_names (const char *);
static int name_search (const void *, const void *);
static int name_sort (const void *, const void *);
static char *canonicalize_name (const char *);
static unsigned find_source (const char *);
static function_t *read_graph_file (void);
static int read_count_file (function_t *);
@ -353,6 +366,7 @@ 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 char *mangle_name (const char *, char *);
static void release_structures (void);
static void release_function (function_t *);
extern int main (int, char **);
@ -414,7 +428,7 @@ 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, "Usage: gcov [OPTION]... SOURCE|OBJ...\n\n");
fnotice (file, "Print code coverage information.\n\n");
fnotice (file, " -h, --help Print this help, then exit\n");
fnotice (file, " -v, --version Print version number, then exit\n");
@ -524,7 +538,7 @@ process_args (int argc, char **argv)
return optind;
}
/* Process a single source file. */
/* Process a single input file. */
static void
process_file (const char *file_name)
@ -622,6 +636,16 @@ generate_results (const char *file_name)
}
}
if (file_name)
{
name_map_t *name_map = (name_map_t *)bsearch
(file_name, names, n_names, sizeof (*names), name_search);
if (name_map)
file_name = sources[name_map->src].name;
else
file_name = canonicalize_name (file_name);
}
for (ix = n_sources, src = sources; ix--; src++)
{
accumulate_line_counts (src);
@ -681,10 +705,12 @@ release_structures (void)
function_t *fn;
for (ix = n_sources; ix--;)
{
free (sources[ix].name);
free (sources[ix].lines);
}
free (sources[ix].lines);
free (sources);
for (ix = n_names; ix--;)
free (names[ix].name);
free (names);
while ((fn = functions))
{
@ -761,28 +787,75 @@ create_file_names (const char *file_name)
return;
}
/* A is a string and B is a pointer to name_map_t. Compare for file
name orderability. */
static int
name_search (const void *a_, const void *b_)
{
const char *a = (const char *)a_;
const name_map_t *b = (const name_map_t *)b_;
#if HAVE_DOS_BASED_FILE_SYSTEM
return strcasecmp (a, b->name);
#else
return strcmp (a, b->name);
#endif
}
/* A and B are a pointer to name_map_t. Compare for file name
orderability. */
static int
name_sort (const void *a_, const void *b_)
{
const name_map_t *a = (const name_map_t *)a_;
return name_search (a->name, b_);
}
/* Find or create a source file structure for FILE_NAME. Copies
FILE_NAME on creation */
static unsigned
find_source (const char *file_name)
{
unsigned ix;
source_t *src = 0;
name_map_t *name_map;
char *canon;
unsigned idx;
struct stat status;
if (!file_name)
file_name = "<unknown>";
for (ix = n_sources; ix--;)
if (!filename_cmp (file_name, sources[ix].name))
{
src = &sources[ix];
break;
}
if (!src)
name_map = (name_map_t *)bsearch
(file_name, names, n_names, sizeof (*names), name_search);
if (name_map)
{
idx = name_map->src;
goto check_date;
}
if (n_names + 2 > a_names)
{
/* Extend the name map array -- we'll be inserting one or two
entries. */
if (!a_names)
a_names = 10;
a_names *= 2;
name_map = XNEWVEC (name_map_t, a_names);
memcpy (name_map, names, n_names * sizeof (*names));
free (names);
names = name_map;
}
/* Not found, try the canonical name. */
canon = canonicalize_name (file_name);
name_map = (name_map_t *)bsearch
(canon, names, n_names, sizeof (*names), name_search);
if (!name_map)
{
/* Not found with canonical name, create a new source. */
source_t *src;
if (n_sources == a_sources)
{
if (!a_sources)
@ -793,31 +866,51 @@ find_source (const char *file_name)
free (sources);
sources = src;
}
ix = n_sources;
src = &sources[ix];
src->name = xstrdup (file_name);
idx = n_sources;
name_map = &names[n_names++];
name_map->name = canon;
name_map->src = idx;
src = &sources[n_sources++];
memset (src, 0, sizeof (*src));
src->name = canon;
src->coverage.name = src->name;
n_sources++;
if (!stat (file_name, &status))
if (!stat (src->name, &status))
src->file_time = status.st_mtime;
}
else
idx = name_map->src;
if (src->file_time > bbg_file_time)
if (name_search (file_name, name_map))
{
/* Append the non-canonical name. */
name_map = &names[n_names++];
name_map->name = xstrdup (file_name);
name_map->src = idx;
}
/* Resort the name map. */
qsort (names, n_names, sizeof (*names), name_sort);
check_date:
if (sources[idx].file_time > bbg_file_time)
{
static int info_emitted;
fnotice (stderr, "%s:source file is newer than graph file '%s'\n",
src->name, bbg_file_name);
file_name, bbg_file_name);
if (!info_emitted)
{
fnotice (stderr,
"(the message is only displayed one per source file)\n");
info_emitted = 1;
}
src->file_time = 0;
sources[idx].file_time = 0;
}
return ix;
return idx;
}
/* Read the graph file. Return list of functions read -- in reverse order. */
@ -1510,97 +1603,169 @@ function_summary (const coverage_t *coverage, const char *title)
}
}
/* 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
/* 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 buf;
*ptr = 0;
if (dd_base == ptr || stat (result, &buf) || S_ISLNK (buf.st_mode))
{
/* 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 '##'. Also '.' filename components are
removed and '..' components are renamed to '^'. */
components are separated by '##'. With preserve_paths we create a
filename from all path components of the source file, replacing '/'
with '#', and .. with '^', without it we simply take the basename
component. (Remember, the canonicalized name will already have
elided '.' components and converted \\ separators.) */
static char *
make_gcov_file_name (const char *input_name, const char *src_name)
{
const char *cptr;
char *name;
char *ptr;
char *result;
if (flag_long_names && input_name && strcmp (src_name, input_name))
{
name = XNEWVEC (char, strlen (src_name) + strlen (input_name) + 10);
name[0] = 0;
/* Generate the input filename part. */
cptr = flag_preserve_paths ? NULL : lbasename (input_name);
strcat (name, cptr ? cptr : input_name);
strcat (name, "##");
result = XNEWVEC (char, strlen (input_name) + strlen (src_name) + 10);
ptr = result;
ptr = mangle_name (input_name, ptr);
ptr[0] = ptr[1] = '#';
ptr += 2;
}
else
{
name = XNEWVEC (char, strlen (src_name) + 10);
name[0] = 0;
result = XNEWVEC (char, strlen (src_name) + 10);
ptr = result;
}
ptr = mangle_name (src_name, ptr);
strcpy (ptr, ".gcov");
return result;
}
static char *
mangle_name (char const *base, char *ptr)
{
size_t len;
/* Generate the source filename part. */
cptr = flag_preserve_paths ? NULL : lbasename (src_name);
strcat (name, cptr ? cptr : src_name);
if (flag_preserve_paths)
if (!flag_preserve_paths)
{
/* Convert '/' and '\' to '#', remove '/./', convert '/../' to '#^#',
convert ':' to '~' on DOS based file system. */
char *pnew = name, *pold = name;
/* First check for leading drive separator. */
while (*pold != '\0')
{
#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
if (*pold == ':')
{
*pnew++ = '~';
pold++;
}
else
#endif
if ((*pold == '/'
&& (strstr (pold, "/./") == pold
|| strstr (pold, "/.\\") == pold))
|| (*pold == '\\'
&& (strstr (pold, "\\.\\") == pold
|| strstr (pold, "\\./") == pold)))
pold += 3;
else if (*pold == '/'
&& (strstr (pold, "/../") == pold
|| strstr (pold, "/..\\") == pold))
{
strcpy (pnew, "#^#");
pnew += 3;
pold += 4;
}
else if (*pold == '\\'
&& (strstr (pold, "\\..\\") == pold
|| strstr (pold, "\\../") == pold))
{
strcpy (pnew, "#^#");
pnew += 3;
pold += 4;
}
else if (*pold == '/' || *pold == '\\')
{
*pnew++ = '#';
pold++;
}
else
*pnew++ = *pold++;
}
*pnew = '\0';
base = lbasename (base);
len = strlen (base);
memcpy (ptr, base, len);
ptr += len;
}
else
{
/* Convert '/' to '#', convert '..' to '^',
convert ':' to '~' on DOS based file system. */
const char *probe;
strcat (name, ".gcov");
return name;
#if HAVE_DOS_BASED_FILE_SYSTEM
if (base[0] && base[1] == ':')
{
ptr[0] = base[0];
ptr[1] = '~';
ptr += 2;
base += 2;
}
#endif
for (; *base; base = probe)
{
size_t len;
for (probe = base; *probe; probe++)
if (*probe == '/')
break;
len = probe - base;
if (len == 2 && base[0] == '.' && base[1] == '.')
*ptr++ = '^';
else
{
memcpy (ptr, base, len);
ptr += len;
}
if (*probe)
{
*ptr++ = '#';
probe++;
}
}
}
return ptr;
}
/* Scan through the bb_data for each line in the block, increment

View File

@ -1,3 +1,7 @@
2011-11-12 Nathan Sidwell <nathan@acm.org>
* gcc.misc-tests/gcov-15.c: New.
2011-11-11 Jakub Jelinek <jakub@redhat.com>
PR tree-optimization/51058

View File

@ -0,0 +1,30 @@
/* Test gcov multiple paths to file. */
/* { dg-options "-fprofile-arcs -ftest-coverage" } */
/* { dg-do run { target native } } */
#if !RECURSIVE
#define RECURSIVE 1
#include "./gcov-15.c"
#undef RECURSIVE
#endif
static void __attribute__ ((noinline)) Recursive (void);
#if RECURSIVE
static void __attribute__ ((noinline))
Recursive ()
{
return; /* count(1) */
}
#else
int main ()
{
Recursive (); /* count(1) */
return 0;
}
#endif
/* { dg-final { run-gcov { -a gcov-15.c } } } */