@@ -127,11 +127,14 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
[@option{-g}|@option{--conditions}]
[@option{-d}|@option{--display-progress}]
[@option{-f}|@option{--function-summaries}]
+ [@option{--include} @var{regex}]
+ [@option{--exclude} @var{regex}]
[@option{-j}|@option{--json-format}]
[@option{-H}|@option{--human-readable}]
[@option{-k}|@option{--use-colors}]
[@option{-l}|@option{--long-file-names}]
[@option{-m}|@option{--demangled-names}]
+ [@option{-M}|@option{--filter-on-demangled}]
[@option{-n}|@option{--no-output}]
[@option{-o}|@option{--object-directory} @var{directory|file}]
[@option{-p}|@option{--preserve-paths}]
@@ -186,6 +189,24 @@ Display the progress on the standard output.
@itemx --function-summaries
Output summaries for each function in addition to the file level summary.
+@item --include @var{regex}
+Include functions matching @var{regex}. This option makes
+@command{gcov} only report on functions that match the extended
+regular expression @var{regex}. This flag can be combined with
+@option{--exclude}. If a function matches both includes and excludes,
+the last include/exclude applies. By default @command{gcov} reports
+on all functions, but if a @command{--include} is used then only
+functions matching the include will be reported.
+
+@item --exclude @var{regex}
+Exclude functions matching @var{regex}. This option makes
+@command{gcov} not report on functions that match the extended regular
+expression @var{regex}. This flag can be combined with
+@option{--include}. If a function matches both includes and excludes,
+the last include/exclude applies. By default @command{gcov} reports
+on all functions, and if @option{--exclude} is used then functions
+matching it will be omitted.
+
@item -h
@itemx --help
Display help about using @command{gcov} (on the standard output), and
@@ -449,6 +470,12 @@ and included file names will be complete path names.
Display demangled function names in output. The default is to show
mangled function names.
+@item -M
+@itemx --filter-on-demangled
+Make @option{--include} and @option{--exclude} match demangled names.
+This does only affects the matching and does not imply
+@option{--demangled-names}, but it can safely be combined with it.
+
@item -n
@itemx --no-output
Do not create the @command{gcov} output file.
@@ -47,10 +47,10 @@ along with Gcov; see the file COPYING3. If not see
#include "pretty-print.h"
#include "json.h"
#include "hwint.h"
+#include "xregex.h"
#include <zlib.h>
#include <getopt.h>
-
#include "md5.h"
using namespace std;
@@ -683,6 +683,48 @@ static int flag_counts = 0;
/* Return code of the tool invocation. */
static int return_code = 0;
+/* "Keep policy" when adding functions to the global function table. This will
+ be set to false when --include is used, otherwise every function should be
+ added to the table. Used for --include/exclude. */
+static bool default_keep = true;
+
+/* Include/exclude filters function based on matching the (de)mangled name.
+ The default is to match the mangled name. Note that flag_demangled_names
+ does not affect this. */
+static bool flag_filter_on_demangled = false;
+
+/* A 'function filter', a filter and action for determining if a function
+ should be included in the output or not. Used for --include/--exclude
+ filtering. */
+struct fnfilter
+{
+ /* The (extended) compiled regex for this filter. */
+ regex_t regex;
+
+ /* The action when this filter (regex) matches - if true, the function should
+ be kept, otherwise discarded. */
+ bool keep;
+
+ /* Compile the regex EXPR, or exit if pattern is malformed. */
+ void compile (const char *expr)
+ {
+ int err = regcomp (®ex, expr, REG_NOSUB | REG_EXTENDED);
+ if (err)
+ {
+ size_t len = regerror (err, ®ex, nullptr, 0);
+ char *msg = XNEWVEC (char, len);
+ regerror (err, ®ex, msg, len);
+ fprintf (stderr, "Bad regular expression: %s\n", msg);
+ free (msg);
+ exit (EXIT_FAILURE);
+ }
+ }
+};
+
+/* A collection of filter functions for including/exclude functions in the
+ output. This is empty unless --include/--exclude is used. */
+static vector<fnfilter> filters;
+
/* Forward declarations. */
static int process_args (int, char **);
static void print_usage (int) ATTRIBUTE_NORETURN;
@@ -977,6 +1019,8 @@ print_usage (int error_p)
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, " --include Include functions matching this regex\n");
+ fnotice (file, " --exclude Exclude functions matching this regex\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");
@@ -985,6 +1029,8 @@ print_usage (int error_p)
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, " -M, --filter-on-demangled Make --include/--exclude match on demangled\n\
+ names. This does not imply -m\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");
@@ -1029,11 +1075,14 @@ static const struct option options[] =
{ "branch-counts", no_argument, NULL, 'c' },
{ "conditions", no_argument, NULL, 'g' },
{ "json-format", no_argument, NULL, 'j' },
+ { "include", required_argument, NULL, 'I' },
+ { "exclude", required_argument, NULL, 'E' },
{ "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' },
+ { "filter-on-demangled", no_argument, NULL, 'M' },
{ "preserve-paths", no_argument, NULL, 'p' },
{ "relative-only", no_argument, NULL, 'r' },
{ "object-directory", required_argument, NULL, 'o' },
@@ -1056,7 +1105,7 @@ process_args (int argc, char **argv)
{
int opt;
- const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
+ const char *opts = "abcdDfghHijklmMno:pqrs:tuvwx";
while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
{
switch (opt)
@@ -1082,6 +1131,17 @@ process_args (int argc, char **argv)
case 'l':
flag_long_names = 1;
break;
+ case 'I':
+ default_keep = false;
+ filters.push_back (fnfilter {});
+ filters.back ().keep = true;
+ filters.back ().compile (optarg);
+ break;
+ case 'E':
+ filters.push_back (fnfilter {});
+ filters.back ().keep = false;
+ filters.back ().compile (optarg);
+ break;
case 'H':
flag_human_readable_numbers = 1;
break;
@@ -1094,6 +1154,9 @@ process_args (int argc, char **argv)
case 'm':
flag_demangled_names = 1;
break;
+ case 'M':
+ flag_filter_on_demangled = true;
+ break;
case 'n':
flag_gcov_file = 0;
break;
@@ -1728,9 +1791,13 @@ release_structures (void)
it != functions.end (); it++)
delete (*it);
+ for (fnfilter &filter : filters)
+ regfree (&filter.regex);
+
sources.resize (0);
names.resize (0);
functions.resize (0);
+ filters.resize (0);
ident_to_fn.clear ();
}
@@ -1955,8 +2022,6 @@ read_graph_file (void)
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;
@@ -1970,6 +2035,26 @@ read_graph_file (void)
fn->artificial = artificial;
current_tag = tag;
+
+ /* This is separate from flag_demangled_names to support filtering on
+ mangled names while printing demangled names, or filtering on
+ demangled names while printing mangled names. An independent flag
+ makes sure the function selection does not change even if
+ demangling is turned on/off. */
+ const char *fname = function_name;
+ if (flag_filter_on_demangled)
+ fname = fn->get_demangled_name ();
+
+ bool keep = default_keep;
+ for (const fnfilter &fn : filters)
+ if (regexec (&fn.regex, fname, 0, nullptr, 0) == 0)
+ keep = fn.keep;
+
+ if (keep)
+ {
+ functions.push_back (fn);
+ ident_to_fn[ident] = fn;
+ }
}
else if (fn && tag == GCOV_TAG_BLOCKS)
{
@@ -3377,11 +3462,17 @@ output_lines (FILE *gcov_file, const source_info *src)
unsigned line_start_group = 0;
vector<function_info *> *fns;
+ unsigned filtered_line_end = !filters.empty () ? 0 : source_lines.size ();
for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++)
{
if (line_num >= src->lines.size ())
{
+ /* If the src->lines is truncated because the rest of the functions
+ are filtered out we must stop here, and not fall back to printing
+ the rest of the file. */
+ if (!filters.empty ())
+ break;
fprintf (gcov_file, "%9s:%5u", "-", line_num);
print_source_line (gcov_file, source_lines, line_num);
continue;
@@ -3400,11 +3491,26 @@ output_lines (FILE *gcov_file, const source_info *src)
for (unsigned i = 0; i < fns->size (); i++)
if ((*fns)[i]->end_line > line_start_group)
line_start_group = (*fns)[i]->end_line;
+
+ /* When filtering, src->lines will be cut short for the last
+ selected function. To make sure the "overlapping function"
+ section is printed too, adjust the end so that it is within
+ src->lines. */
+ if (line_start_group >= src->lines.size ())
+ line_start_group = src->lines.size () - 1;
+
+ if (!filters.empty ())
+ filtered_line_end = line_start_group;
}
else if (fns != NULL && fns->size () == 1)
{
function_info *fn = (*fns)[0];
output_function_details (gcov_file, fn);
+
+ /* If functions are filtered, only the matching functions will be in
+ fns and there is no need for extra checking. */
+ if (!filters.empty ())
+ filtered_line_end = fn->end_line;
}
}
@@ -3414,12 +3520,16 @@ output_lines (FILE *gcov_file, const source_info *src)
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);
+ if (line_num <= filtered_line_end)
+ {
+ 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);
+ print_source_line (gcov_file, source_lines, line_num);
+ output_line_details (gcov_file, line, line_num);
+ }
if (line_start_group == line_num)
{
new file mode 100644
@@ -0,0 +1,35 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* Filtering on the function base name generally works well, because it becomes
+ an unadultered part of the symbol. */
+
+template <typename T>
+T
+fn1 (T x)
+{
+ /* fst */
+ return x;
+}
+
+template <typename T>
+T
+fn2 (T x)
+{
+ /* snd */
+ return 2 * x;
+}
+
+int
+main ()
+{
+ fn1 (2);
+ fn1 (2.0);
+ fn1 (2.0f);
+
+ fn2 (2);
+ fn2 (2.0);
+ fn2 (2.0f);
+}
+
+/* { dg-final { run-gcov { filters { fn1 } { fn2 } } { --include=fn1 gcov-19.C } } } */
new file mode 100644
@@ -0,0 +1,38 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* Filtering also works by targeting the mangled symbol directly, but the
+ subtlety is not really caught by the test framework. Matching on fn1I[df]
+ prints the "overlapping blocks" of both the float and double instantiation,
+ but *not* the int instantiation. The extra {} around the --include argument
+ is quoting for tcl/dejagnu. */
+
+template <typename T>
+T
+fn1 (T x)
+{
+ /* fst */
+ return x;
+}
+
+template <typename T>
+T
+fn2 (T x)
+{
+ /* snd */
+ return 2 * x;
+}
+
+int
+main ()
+{
+ fn1 (2);
+ fn1 (2.0);
+ fn1 (2.0f);
+
+ fn2 (2);
+ fn2 (2.0);
+ fn2 (2.0f);
+}
+
+/* { dg-final { run-gcov { filters { fst } { snd } } { {--include=fn1I[fd]} gcov-20.C } } } */
new file mode 100644
@@ -0,0 +1,32 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* Filters can be applied to demangled names. This support matching on
+ types and class hierarchies as well as function names. */
+
+template <typename T>
+T
+fn1 (T x)
+{
+ /* fst */
+ return x;
+}
+
+template <typename T>
+T
+fn2 (T x)
+{
+ /* snd */
+ return 2 * x;
+}
+
+int
+main ()
+{
+ fn1 (2);
+
+ fn2 (2.0);
+ fn2 (2.0f);
+}
+
+/* { dg-final { run-gcov { filters { fst } } { --filter-on-demangled --include int gcov-21.C } } } */
new file mode 100644
@@ -0,0 +1,25 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* Filters are considered in order with latest-wins, so if a function is
+ included and later excluded it should not show up. */
+
+int
+fn1 (int x)
+{
+ /* fst */
+ return x;
+}
+
+int
+fn2 (int x)
+{
+ /* snd */
+ return x * 2;
+}
+
+int
+main ()
+{}
+
+/* { dg-final { run-gcov { filters { snd } { fst main } } { --include=fn --exclude=1 gcov-25.c } } } */
new file mode 100644
@@ -0,0 +1,25 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* Filters are considered in order with latest-wins, so if a function is
+ excluded and later included it should show up. */
+
+int
+fn1 (int x)
+{
+ /* fst */
+ return x;
+}
+
+int
+fn2 (int x)
+{
+ /* snd */
+ return x * 2;
+}
+
+int
+main ()
+{}
+
+/* { dg-final { run-gcov { filters { fst snd } { main } } { --exclude=1 --include=fn gcov-26.c } } } */
new file mode 100644
@@ -0,0 +1,24 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* If only --exclude is used, other functions should be used by default. */
+
+int
+fn1 (int x)
+{
+ /* fst */
+ return x;
+}
+
+int
+fn2 (int x)
+{
+ /* snd */
+ return x * 2;
+}
+
+int
+main ()
+{}
+
+/* { dg-final { run-gcov { filters { fst snd } { main } } { --exclude=main gcov-27.c } } } */
new file mode 100644
@@ -0,0 +1,22 @@
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+int
+once (int x)
+{
+ /* fst */
+ return x;
+}
+
+int
+twice (int x)
+{
+ /* snd */
+ return x * 2;
+}
+
+int
+main ()
+{}
+
+/* { dg-final { run-gcov { filters { fst } { snd main } } { --include=once gcov-28.c } } } */
@@ -495,6 +495,44 @@ proc verify-calls { testname testcase file } {
return $failed
}
+# Verify that report filtering includes and excludes the right functions.
+proc verify-filters { testname testcase file expected unexpected } {
+ set fd [open $file r]
+
+ set seen {}
+ set ex [concat $expected $unexpected]
+
+ while { [gets $fd line] >= 0 } {
+ foreach sym $ex {
+ if [regexp "$sym" "$line"] {
+ lappend seen $sym
+ }
+ }
+ }
+
+ set seen [lsort -unique $seen]
+
+ set expected [lmap key $expected {
+ if { $key in $seen } continue
+ set key
+ }]
+ set unexpected [lmap key $unexpected {
+ if { $key ni $seen } continue
+ set key
+ }]
+
+ foreach sym $expected {
+ fail "Did not see expected symbol '$sym'"
+ }
+
+ foreach sym $unexpected {
+ fail "Found unexpected symbol '$sym'"
+ }
+
+ close $fd
+ return [expr [llength $expected] + [llength $unexpected]]
+}
+
proc gcov-pytest-format-line { args } {
global subdir
@@ -570,6 +608,7 @@ proc run-gcov { args } {
set gcov_verify_conditions 0
set gcov_verify_lines 1
set gcov_verify_intermediate 0
+ set gcov_verify_filters 0
set gcov_remove_gcda 0
set xfailed 0
@@ -580,12 +619,17 @@ proc run-gcov { args } {
set gcov_verify_branches 1
} elseif { $a == "conditions" } {
set gcov_verify_conditions 1
+ } elseif { [lindex $a 0] == "filters" } {
+ set gcov_verify_filters 1
+ set verify_filters_expected [lindex $a 1]
+ set verify_filters_unexpected [lindex $a 2]
} elseif { $a == "intermediate" } {
set gcov_verify_intermediate 1
set gcov_verify_calls 0
set gcov_verify_branches 0
set gcov_verify_conditions 0
set gcov_verify_lines 0
+ set gcov_verify_filters 0
} elseif { $a == "remove-gcda" } {
set gcov_remove_gcda 1
} elseif { $gcov_args == "" } {
@@ -670,15 +714,20 @@ proc run-gcov { args } {
} else {
set ifailed 0
}
+ if { $gcov_verify_filters } {
+ set ffailed [verify-filters $testname $testcase $testcase.gcov $verify_filters_expected $verify_filters_unexpected]
+ } else {
+ set ffailed 0
+ }
# Report whether the gcov test passed or failed. If there were
# multiple failures then the message is a summary.
- set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
+ set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed + $ffailed]
if { $xfailed } {
setup_xfail "*-*-*"
}
if { $tfailed > 0 } {
- fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
+ fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format, $ffailed failed in filters"
if { $xfailed } {
clean-gcov $testcase
}