Add condition coverage (MC/DC)

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fcondition-coverage flag. MC/DC is a type of
test/code coverage and it is particularly important for safety-critical
applicaitons in industries like aviation and automotive. Notably, MC/DC
is required or recommended by:

    * DO-178C for the most critical software (Level A) in avionics.
    * IEC 61508 for SIL 4.
    * ISO 26262-6 for ASIL D.

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

MC/DC comes in different flavors, the most important being unique cause
MC/DC and masking MC/DC. This patch implements masking MC/DC, which is
works well with short circuiting semantics, and according to John
Chilenski's "An Investigation of Three Forms of the Modified Condition
Decision Coverage (MCDC) Criterion" (2001) is as good as unique cause at
catching bugs.

Whalen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for finding the masking table from an AST
walk, but my algorithm figures this out by analyzing the control flow
graph.  The CFG is considered a reduced ordered binary decision diagram
and an input vector a path through the BDD, which is recorded.  Specific
edges will mask ("null out") the contribution from earlier path
segments, which can be determined by finding short circuit endpoints.
Masking is most easily understood as circuiting of terms in the
reverse-ordered Boolean function, and the masked conditions do not
affect the decision like short-circuited conditions do not affect the
decision.

A tag/discriminator mapping from gcond->uid is created during
gimplification and made available through the function struct. The
values are unimportant as long as basic conditions constructed from a
single Boolean expression are given the same identifier. This happens in
the breaking down of ANDIF/ORIF trees, so the coverage generally works
well for frontends that create such trees.

Like Whalen et al this implementation records coverage in fixed-size
bitsets which gcov knows how to interpret. Recording conditions only
requires a few bitwise operations per condition and is very fast, but
comes with a limit on the number of terms in a single boolean
expression; the number of bits in a gcov_unsigned_type (which is usually
typedef'd to uint64_t). For most practical purposes this is acceptable,
and by default a warning will be issued if gcc cannot instrument the
expression.  This is a practical limitation in the implementation, and
not a limitation of the algorithm, so support for more conditions can be
supported by introducing arbitrary-sized bitsets.

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
conditions covered 3/8
condition  0 not covered (true false)
condition  1 not covered (true)
condition  2 not covered (true)
condition  3 not covered (true)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

"conditions": [
    {
        "not_covered_false": [
            0
        ],
        "count": 8,
        "covered": 3,
        "not_covered_true": [
            0,
            1,
            2,
            3
        ]
    }
],

Expressions with constants may be heavily rewritten before it reaches
the gimplification, so constructs like int x = a ? 0 : 1 becomes
_x = (_a == 0). From source you would expect coverage, but it gets
neither branch nor condition coverage. The same applies to expressions
like int x = 1 || a which are simply replaced by a constant.

The test suite contains a lot of small programs and functions. Some of
these were designed by hand to test for specific behaviours and graph
shapes, and some are previously-failed test cases in other programs
adapted into the test suite.

gcc/ChangeLog:

	* builtins.cc (expand_builtin_fork_or_exec): Check
	condition_coverage_flag.
	* collect2.cc (main): Add -fno-condition-coverage to OBSTACK.
	* common.opt: Add new options -fcondition-coverage and
	-Wcoverage-too-many-conditions.
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fcondition-coverage documentation.
	* function.cc (free_after_compilation): Free cond_uids.
	* function.h (struct function): Add cond_uids.
	* gcc.cc: Link gcov on -fcondition-coverage.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-dump.cc (tag_conditions): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): New.
	(GCOV_TAG_CONDS_NUM): New.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read condition counters.
	(read_count_file): Likewise.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* gimplify.cc (next_cond_uid): New.
	(reset_cond_uid): New.
	(shortcut_cond_r): Set condition discriminator.
	(tag_shortcut_cond): New.
	(gimple_associate_condition_with_expr): New.
	(shortcut_cond_expr): Set condition discriminator.
	(gimplify_cond_expr): Likewise.
	(gimplify_function_tree): Call reset_cond_uid.
	* ipa-inline.cc (can_early_inline_edge_p): Check
	condition_coverage_flag.
	* ipa-split.cc (pass_split_functions::gate): Likewise.
	* passes.cc (finish_optimization_passes): Likewise.
	* profile.cc (struct condcov): New declaration.
	(cov_length): Likewise.
	(cov_blocks): Likewise.
	(cov_masks): Likewise.
	(cov_maps): Likewise.
	(cov_free): Likewise.
	(instrument_decisions): New.
	(read_thunk_profile): Control output to file.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-core.h (struct tree_exp): Add condition_uid.
	* tree-profile.cc (struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(EDGE_CONDITION): New.
	(topological_cmp): New.
	(index_of): New.
	(single_p): New.
	(single_edge): New.
	(contract_edge_up): New.
	(struct outcomes): New.
	(conditional_succs): New.
	(condition_index): New.
	(condition_uid): New.
	(masking_vectors): New.
	(emit_assign): New.
	(emit_bitwise_op): New.
	(make_top_index_visit): New.
	(make_top_index): New.
	(paths_between): New.
	(struct condcov): New.
	(cov_length): New.
	(cov_blocks): New.
	(cov_masks): New.
	(cov_maps): New.
	(cov_free): New.
	(find_conditions): New.
	(struct counters): New.
	(find_counters): New.
	(resolve_counter): New.
	(resolve_counters): New.
	(instrument_decisions): New.
	(tree_profiling): Check condition_coverage_flag.
	(pass_ipa_tree_profile::gate): Likewise.
	* tree.h (SET_EXPR_UID): New.
	(EXPR_COND_UID): New.

libgcc/ChangeLog:

	* libgcov-merge.c (__gcov_merge_ior): New.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp: Add condition coverage test function.
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
	* gcc.misc-tests/gcov-20.c: New test.
	* gcc.misc-tests/gcov-21.c: New test.
	* gcc.misc-tests/gcov-22.c: New test.
	* gcc.misc-tests/gcov-23.c: New test.
This commit is contained in:
Jørgen Kvalsvik 2023-12-05 12:59:40 +01:00
parent b7bd2ec73d
commit 08a5233180
28 changed files with 4339 additions and 42 deletions

View File

@ -6329,7 +6329,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
tree call;
/* If we are not profiling, just call the function. */
if (!profile_arc_flag)
if (!profile_arc_flag && !condition_coverage_flag)
return NULL_RTX;
/* Otherwise call the wrapper. This should be equivalent for the rest of

View File

@ -1035,9 +1035,9 @@ main (int argc, char **argv)
lto_mode = LTO_MODE_LTO;
}
/* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
-fno-exceptions -w -fno-whole-program */
num_c_args += 6;
/* -fno-profile-arcs -fno-condition-coverage -fno-test-coverage
-fno-branch-probabilities -fno-exceptions -w -fno-whole-program */
num_c_args += 7;
c_argv = XCNEWVEC (char *, num_c_args);
c_ptr = CONST_CAST2 (const char **, char **, c_argv);
@ -1233,6 +1233,7 @@ main (int argc, char **argv)
}
obstack_free (&temporary_obstack, temporary_firstobj);
*c_ptr++ = "-fno-profile-arcs";
*c_ptr++ = "-fno-condition-coverage";
*c_ptr++ = "-fno-test-coverage";
*c_ptr++ = "-fno-branch-probabilities";
*c_ptr++ = "-fno-exceptions";

View File

@ -870,6 +870,11 @@ Wcoverage-invalid-line-number
Common Var(warn_coverage_invalid_linenum) Init(1) Warning
Warn in case a function ends earlier than it begins due to an invalid linenum macros.
Wcoverage-too-many-conditions
Common Var(warn_too_many_conditions) Init(1) Warning
Warn when a conditional has too many terms and condition coverage profiling
gives up instrumenting the expression.
Wmissing-profile
Common Var(warn_missing_profile) Init(1) Warning
Warn in case profiles in -fprofile-use do not exist.
@ -2464,6 +2469,10 @@ fprofile-arcs
Common Var(profile_arc_flag)
Insert arc-based program profiling code.
fcondition-coverage
Common Var(condition_coverage_flag)
Insert condition coverage profiling code.
fprofile-dir=
Common Joined RejectNegative Var(profile_data_prefix)
Set the top-level directory for storing the profile data.

View File

@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
[@option{-a}|@option{--all-blocks}]
[@option{-b}|@option{--branch-probabilities}]
[@option{-c}|@option{--branch-counts}]
[@option{-g}|@option{--conditions}]
[@option{-d}|@option{--display-progress}]
[@option{-f}|@option{--function-summaries}]
[@option{-j}|@option{--json-format}]
@ -169,6 +170,14 @@ be shown, unless the @option{-u} option is given.
Write branch frequencies as the number of branches taken, rather than
the percentage of branches taken.
@item -g
@itemx --conditions
Write condition coverage to the output file, and write condition summary info
to the standard output. This option allows you to see if the conditions in
your program at least once had an independent effect on the outcome of the
boolean expression (modified condition/decision coverage). This requires you
to compile the source with @option{-fcondition-coverage}.
@item -d
@itemx --display-progress
Display the progress on the standard output.
@ -301,6 +310,7 @@ Each @var{line} has the following form:
"branches": ["$branch"],
"calls": ["$call"],
"count": 2,
"conditions": ["$condition"],
"line_number": 15,
"unexecuted_block": false,
"function_name": "foo",
@ -384,6 +394,34 @@ to @var{line::count})
@var{destination_block_id}: ID of the basic block this calls continues after return
@end itemize
Each @var{condition} has the following form:
@smallexample
@{
"count": 4,
"covered": 2,
"not_covered_false": [],
"not_covered_true": [0, 1],
@}
@end smallexample
Fields of the @var{condition} element have following semantics:
@itemize @bullet
@item
@var{count}: number of condition outcomes in this expression
@item
@var{covered}: number of covered condition outcomes in this expression
@item
@var{not_covered_true}: terms, by index, not seen as true in this expression
@item
@var{not_covered_false}: terms, by index, not seen as false in this expression
@end itemize
@item -H
@itemx --human-readable
Write counts in human readable format (like 24.6k).

View File

@ -640,6 +640,7 @@ Objective-C and Objective-C++ Dialects}.
@item Program Instrumentation Options
@xref{Instrumentation Options,,Program Instrumentation Options}.
@gccoptlist{-p -pg -fprofile-arcs --coverage -ftest-coverage
-fcondition-coverage
-fprofile-abs-path
-fprofile-dir=@var{path} -fprofile-generate -fprofile-generate=@var{path}
-fprofile-info-section -fprofile-info-section=@var{name}
@ -6577,6 +6578,14 @@ poorly optimized code and is useful only in the
case of very minor changes such as bug fixes to an existing code-base.
Completely disabling the warning is not recommended.
@opindex Wno-coverage-too-many-conditions
@opindex Wcoverage-too-many-conditions
@item -Wno-coverage-too-many-conditions
Warn if @option{-fcondition-coverage} is used and an expression have too many
terms and GCC gives up coverage. Coverage is given up when there are more
terms in the conditional than there are bits in a @code{gcov_type_unsigned}.
This warning is enabled by default.
@opindex Wno-coverage-invalid-line-number
@opindex Wcoverage-invalid-line-number
@item -Wno-coverage-invalid-line-number
@ -17078,6 +17087,14 @@ Note that if a command line directly links source files, the corresponding
E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
@file{binary-b.gcda} files.
@item -fcondition-coverage
@opindex fcondition-coverage
Add code so that program conditions are instrumented. During execution the
program records what terms in a conditional contributes to a decision, which
can be used to verify that all terms in a Boolean function are tested and have
an independent effect on the outcome of a decision. The result can be read
with @code{gcov --conditions}.
@xref{Cross-profiling}.
@cindex @command{gcov}
@ -17140,6 +17157,10 @@ executed. When an arc is the only exit or only entrance to a block, the
instrumentation code can be added to the block; otherwise, a new basic
block must be created to hold the instrumentation code.
With @option{-fcondition-coverage}, for each conditional in your program GCC
creates a bitset and records the exercised boolean values that have an
independent effect on the outcome of that expression.
@need 2000
@opindex ftest-coverage
@item -ftest-coverage

View File

@ -216,6 +216,7 @@ free_after_compilation (struct function *f)
f->machine = NULL;
f->cfg = NULL;
f->curr_properties &= ~PROP_cfg;
delete f->cond_uids;
regno_reg_rtx = NULL;
}

View File

@ -270,6 +270,10 @@ struct GTY(()) function {
/* Value histograms attached to particular statements. */
htab_t GTY((skip)) value_histograms;
/* Annotated gconds so that basic conditions in the same expression map to
the same same uid. This is used for condition coverage. */
hash_map <gcond*, unsigned> *GTY((skip)) cond_uids;
/* For function.cc. */
/* Points to the FUNCTION_DECL of this function. */

View File

@ -1165,7 +1165,7 @@ proper position among the other output files. */
%:include(libgomp.spec)%(link_gomp)}\
%{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
" STACK_SPLIT_SPEC "\
%{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
%{fprofile-arcs|fcondition-coverage|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
%{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
%{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*} \n%(post_link) }}}}}}"
#endif
@ -1288,7 +1288,7 @@ static const char *cc1_options =
%{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
%{fsyntax-only:-o %j} %{-param*}\
%{coverage:-fprofile-arcs -ftest-coverage}\
%{fprofile-arcs|fprofile-generate*|coverage:\
%{fprofile-arcs|fcondition-coverage|fprofile-generate*|coverage:\
%{!fprofile-update=single:\
%{pthread:-fprofile-update=prefer-atomic}}}";

View File

@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
/* Time profile collecting first run of a function */
DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
/* Conditions. The counter is interpreted as a bit-set. */
DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)

View File

@ -38,6 +38,7 @@ static void print_version (void);
static void tag_function (const char *, unsigned, int, unsigned);
static void tag_blocks (const char *, unsigned, int, unsigned);
static void tag_arcs (const char *, unsigned, int, unsigned);
static void tag_conditions (const char *, unsigned, int, unsigned);
static void tag_lines (const char *, unsigned, int, unsigned);
static void tag_counters (const char *, unsigned, int, unsigned);
static void tag_summary (const char *, unsigned, int, unsigned);
@ -77,6 +78,7 @@ static const tag_format_t tag_table[] =
{GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
{GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
{GCOV_TAG_ARCS, "ARCS", tag_arcs},
{GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
{GCOV_TAG_LINES, "LINES", tag_lines},
{GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
{0, NULL, NULL}
@ -392,6 +394,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
}
}
/* Print number of conditions (not outcomes, i.e. if (x && y) is 2, not 4). */
static void
tag_conditions (const char *filename, unsigned /* tag */, int length,
unsigned depth)
{
unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
printf (" %u conditions", n_conditions);
if (flag_dump_contents)
{
for (unsigned ix = 0; ix != n_conditions; ix++)
{
const unsigned blockno = gcov_read_unsigned ();
const unsigned nterms = gcov_read_unsigned ();
printf ("\n");
print_prefix (filename, depth, gcov_position ());
printf (VALUE_PADDING_PREFIX "block %u:", blockno);
printf (" %u", nterms);
}
}
}
static void
tag_lines (const char *filename ATTRIBUTE_UNUSED,
unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,

View File

@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
#define GCOV_TAG_ARCS ((gcov_unsigned_t)0x01430000)
#define GCOV_TAG_ARCS_LENGTH(NUM) (1 + (NUM) * 2 * GCOV_WORD_SIZE)
#define GCOV_TAG_ARCS_NUM(LENGTH) (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
#define GCOV_TAG_CONDS ((gcov_unsigned_t)0x01470000)
#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
#define GCOV_TAG_LINES ((gcov_unsigned_t)0x01450000)
#define GCOV_TAG_COUNTER_BASE ((gcov_unsigned_t)0x01a10000)
#define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)

View File

@ -46,6 +46,7 @@ along with Gcov; see the file COPYING3. If not see
#include "color-macros.h"
#include "pretty-print.h"
#include "json.h"
#include "hwint.h"
#include <zlib.h>
#include <getopt.h>
@ -81,6 +82,7 @@ using namespace std;
class function_info;
class block_info;
class source_info;
class condition_info;
/* Describes an arc between two basic blocks. */
@ -134,6 +136,33 @@ public:
vector<unsigned> lines;
};
/* Describes a single conditional expression and the (recorded) conditions
shown to independently affect the outcome. */
class condition_info
{
public:
condition_info ();
int popcount () const;
/* Bitsets storing the independently significant outcomes for true and false,
respectively. */
gcov_type_unsigned truev;
gcov_type_unsigned falsev;
/* Number of terms in the expression; if (x) -> 1, if (x && y) -> 2 etc. */
unsigned n_terms;
};
condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
{
}
int condition_info::popcount () const
{
return popcount_hwi (truev) + popcount_hwi (falsev);
}
/* Describes a basic block. Contains lists of arcs to successor and
predecessor blocks. */
@ -167,6 +196,8 @@ public:
/* Block is a landing pad for longjmp or throw. */
unsigned is_nonlocal_return : 1;
condition_info conditions;
vector<block_location_info> locations;
struct
@ -277,6 +308,8 @@ public:
vector<block_info> blocks;
unsigned blocks_executed;
vector<condition_info*> conditions;
/* Raw arc coverage counts. */
vector<gcov_type> counts;
@ -353,6 +386,9 @@ struct coverage_info
int branches_executed;
int branches_taken;
int conditions;
int conditions_covered;
int calls;
int calls_executed;
@ -552,6 +588,10 @@ static int multiple_files = 0;
static int flag_branches = 0;
/* Output conditions (modified condition/decision coverage). */
static bool flag_conditions = 0;
/* Show unconditional branches too. */
static int flag_unconditional = 0;
@ -658,6 +698,7 @@ 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_condition_counts (coverage_info *, const block_info *);
static void add_line_counts (coverage_info *, function_info *);
static void executed_summary (unsigned, unsigned);
static void function_summary (const coverage_info *);
@ -666,6 +707,7 @@ 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_conditions (FILE *, const block_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 *);
@ -930,6 +972,8 @@ print_usage (int error_p)
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, " -g, --conditions Include modified condition/decision\n\
coverage in output\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");
@ -983,6 +1027,7 @@ static const struct option options[] =
{ "all-blocks", no_argument, NULL, 'a' },
{ "branch-probabilities", no_argument, NULL, 'b' },
{ "branch-counts", no_argument, NULL, 'c' },
{ "conditions", no_argument, NULL, 'g' },
{ "json-format", no_argument, NULL, 'j' },
{ "human-readable", no_argument, NULL, 'H' },
{ "no-output", no_argument, NULL, 'n' },
@ -1011,7 +1056,7 @@ process_args (int argc, char **argv)
{
int opt;
const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
{
switch (opt)
@ -1028,6 +1073,9 @@ process_args (int argc, char **argv)
case 'f':
flag_function_summary = 1;
break;
case 'g':
flag_conditions = 1;
break;
case 'h':
print_usage (false);
/* print_usage will exit. */
@ -1152,6 +1200,45 @@ output_intermediate_json_line (json::array *object,
}
}
json::array *conditions = new json::array ();
lineo->set ("conditions", conditions);
if (flag_conditions)
{
vector<block_info *>::const_iterator it;
for (it = line->blocks.begin (); it != line->blocks.end (); it++)
{
const condition_info& info = (*it)->conditions;
if (info.n_terms == 0)
continue;
const int count = 2 * info.n_terms;
const int covered = info.popcount ();
json::object *cond = new json::object ();
cond->set ("count", new json::integer_number (count));
cond->set ("covered", new json::integer_number (covered));
json::array *mtrue = new json::array ();
json::array *mfalse = new json::array ();
cond->set ("not_covered_true", mtrue);
cond->set ("not_covered_false", mfalse);
if (count != covered)
{
for (unsigned i = 0; i < info.n_terms; i++)
{
gcov_type_unsigned index = 1;
index <<= i;
if (!(index & info.truev))
mtrue->append (new json::integer_number (i));
if (!(index & info.falsev))
mfalse->append (new json::integer_number (i));
}
}
conditions->append (cond);
}
}
object->append (lineo);
}
@ -1969,6 +2056,28 @@ read_graph_file (void)
}
}
}
else if (fn && tag == GCOV_TAG_CONDS)
{
unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
if (!fn->conditions.empty ())
fnotice (stderr, "%s:already seen conditions for '%s'\n",
bbg_file_name, fn->get_name ());
else
fn->conditions.resize (num_dests);
for (unsigned i = 0; i < num_dests; ++i)
{
unsigned idx = gcov_read_unsigned ();
if (idx >= fn->blocks.size ())
goto corrupt;
condition_info *info = &fn->blocks[idx].conditions;
info->n_terms = gcov_read_unsigned ();
fn->conditions[i] = info;
}
}
else if (fn && tag == GCOV_TAG_LINES)
{
unsigned blockno = gcov_read_unsigned ();
@ -2099,6 +2208,21 @@ read_count_file (void)
goto cleanup;
}
}
else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
{
length = abs (read_length);
if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
goto mismatch;
if (read_length > 0)
{
for (ix = 0; ix != fn->conditions.size (); ix++)
{
fn->conditions[ix]->truev |= gcov_read_counter ();
fn->conditions[ix]->falsev |= gcov_read_counter ();
}
}
}
else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
{
length = abs (read_length);
@ -2443,6 +2567,15 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
}
}
/* Increment totals in COVERAGE according to to block BLOCK. */
static void
add_condition_counts (coverage_info *coverage, const block_info *block)
{
coverage->conditions += 2 * block->conditions.n_terms;
coverage->conditions_covered += block->conditions.popcount ();
}
/* Format COUNT, if flag_human_readable_numbers is set, return it human
readable format. */
@ -2546,6 +2679,18 @@ file_summary (const coverage_info *coverage)
coverage->calls);
else
fnotice (stdout, "No calls\n");
}
if (flag_conditions)
{
if (coverage->conditions)
fnotice (stdout, "Condition outcomes covered:%s of %d\n",
format_gcov (coverage->conditions_covered,
coverage->conditions, 2),
coverage->conditions);
else
fnotice (stdout, "No conditions\n");
}
}
@ -2780,6 +2925,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
it != line->branches.end (); it++)
add_branch_counts (&src->coverage, *it);
if (add_coverage)
for (vector<block_info *>::iterator it = line->blocks.begin ();
it != line->blocks.end (); it++)
add_condition_counts (&src->coverage, *it);
if (!line->blocks.empty ())
{
/* The user expects the line count to be the number of times
@ -2881,6 +3032,37 @@ accumulate_line_counts (source_info *src)
}
}
/* Output information about the conditions in block BINFO. The output includes
* a summary (n/m outcomes covered) and a list of the missing (uncovered)
* outcomes. */
static void
output_conditions (FILE *gcov_file, const block_info *binfo)
{
const condition_info& info = binfo->conditions;
if (info.n_terms == 0)
return;
const int expected = 2 * info.n_terms;
const int got = info.popcount ();
fnotice (gcov_file, "condition outcomes covered %d/%d\n", got, expected);
if (expected == got)
return;
for (unsigned i = 0; i < info.n_terms; i++)
{
gcov_type_unsigned index = 1;
index <<= i;
if ((index & info.truev & info.falsev))
continue;
const char *t = (index & info.truev) ? "" : "true";
const char *f = (index & info.falsev) ? "" : " false";
fnotice (gcov_file, "condition %2u not covered (%s%s)\n", i, t, f + !t[0]);
}
}
/* Output information about ARC number IX. Returns nonzero if
anything is output. */
@ -3091,16 +3273,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
if (flag_branches)
for (arc = (*it)->succ; arc; arc = arc->succ_next)
jx += output_branch_count (f, jx, arc);
if (flag_conditions)
output_conditions (f, *it);
}
}
else if (flag_branches)
else
{
int ix;
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));
ix = 0;
for (vector<arc_info *>::const_iterator it = line->branches.begin ();
it != line->branches.end (); it++)
ix += output_branch_count (f, ix, (*it));
}
if (flag_conditions)
{
for (vector<block_info *>::const_iterator it = line->blocks.begin ();
it != line->blocks.end (); it++)
output_conditions (f, *it);
}
}
}

View File

@ -71,6 +71,28 @@ along with GCC; see the file COPYING3. If not see
#include "context.h"
#include "tree-nested.h"
/* Identifier for a basic condition, mapping it to other basic conditions of
its Boolean expression. Basic conditions given the same uid (in the same
function) are parts of the same ANDIF/ORIF expression. Used for condition
coverage. */
static unsigned nextuid = 1;
/* Get a fresh identifier for a new condition expression. This is used for
condition coverage. */
static unsigned
next_cond_uid ()
{
return nextuid++;
}
/* Reset the condition uid to the value it should have when compiling a new
function. 0 is already the default/untouched value, so start at non-zero.
A valid and set id should always be > 0. This is used for condition
coverage. */
static void
reset_cond_uid ()
{
nextuid = 1;
}
/* Hash set of poisoned variables in a bind expr. */
static hash_set<tree> *asan_poisoned_variables = NULL;
@ -4139,13 +4161,16 @@ gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value)
LOCUS is the source location of the COND_EXPR.
The condition_uid is a discriminator tag for condition coverage used to map
conditions to its corresponding full Boolean function.
This function is the tree equivalent of do_jump.
shortcut_cond_r should only be called by shortcut_cond_expr. */
static tree
shortcut_cond_r (tree pred, tree *true_label_p, tree *false_label_p,
location_t locus)
location_t locus, unsigned condition_uid)
{
tree local_label = NULL_TREE;
tree t, expr = NULL;
@ -4167,13 +4192,14 @@ shortcut_cond_r (tree pred, tree *true_label_p, tree *false_label_p,
false_label_p = &local_label;
/* Keep the original source location on the first 'if'. */
t = shortcut_cond_r (TREE_OPERAND (pred, 0), NULL, false_label_p, locus);
t = shortcut_cond_r (TREE_OPERAND (pred, 0), NULL, false_label_p, locus,
condition_uid);
append_to_statement_list (t, &expr);
/* Set the source location of the && on the second 'if'. */
new_locus = rexpr_location (pred, locus);
t = shortcut_cond_r (TREE_OPERAND (pred, 1), true_label_p, false_label_p,
new_locus);
new_locus, condition_uid);
append_to_statement_list (t, &expr);
}
else if (TREE_CODE (pred) == TRUTH_ORIF_EXPR)
@ -4190,13 +4216,14 @@ shortcut_cond_r (tree pred, tree *true_label_p, tree *false_label_p,
true_label_p = &local_label;
/* Keep the original source location on the first 'if'. */
t = shortcut_cond_r (TREE_OPERAND (pred, 0), true_label_p, NULL, locus);
t = shortcut_cond_r (TREE_OPERAND (pred, 0), true_label_p, NULL, locus,
condition_uid);
append_to_statement_list (t, &expr);
/* Set the source location of the || on the second 'if'. */
new_locus = rexpr_location (pred, locus);
t = shortcut_cond_r (TREE_OPERAND (pred, 1), true_label_p, false_label_p,
new_locus);
new_locus, condition_uid);
append_to_statement_list (t, &expr);
}
else if (TREE_CODE (pred) == COND_EXPR
@ -4219,9 +4246,11 @@ shortcut_cond_r (tree pred, tree *true_label_p, tree *false_label_p,
new_locus = rexpr_location (pred, locus);
expr = build3 (COND_EXPR, void_type_node, TREE_OPERAND (pred, 0),
shortcut_cond_r (TREE_OPERAND (pred, 1), true_label_p,
false_label_p, locus),
false_label_p, locus, condition_uid),
shortcut_cond_r (TREE_OPERAND (pred, 2), true_label_p,
false_label_p, new_locus));
false_label_p, new_locus,
condition_uid));
SET_EXPR_UID (expr, condition_uid);
}
else
{
@ -4229,6 +4258,7 @@ shortcut_cond_r (tree pred, tree *true_label_p, tree *false_label_p,
build_and_jump (true_label_p),
build_and_jump (false_label_p));
SET_EXPR_LOCATION (expr, locus);
SET_EXPR_UID (expr, condition_uid);
}
if (local_label)
@ -4279,12 +4309,44 @@ find_goto_label (tree expr)
return NULL_TREE;
}
/* Given a multi-term condition (ANDIF, ORIF), walk the predicate PRED and tag
every basic condition with CONDITION_UID. Two basic conditions share the
CONDITION_UID discriminator when they belong to the same predicate, which is
used by the condition coverage. Doing this as an explicit step makes for a
simpler implementation than weaving it into the splitting code as the
splitting code eventually calls the entry point gimplfiy_expr which makes
bookkeeping complicated. */
static void
tag_shortcut_cond (tree pred, unsigned condition_uid)
{
if (TREE_CODE (pred) == TRUTH_ANDIF_EXPR
|| TREE_CODE (pred) == TRUTH_ORIF_EXPR)
{
tree fst = TREE_OPERAND (pred, 0);
tree lst = TREE_OPERAND (pred, 1);
if (TREE_CODE (fst) == TRUTH_ANDIF_EXPR
|| TREE_CODE (fst) == TRUTH_ORIF_EXPR)
tag_shortcut_cond (fst, condition_uid);
else if (TREE_CODE (fst) == COND_EXPR)
SET_EXPR_UID (fst, condition_uid);
if (TREE_CODE (lst) == TRUTH_ANDIF_EXPR
|| TREE_CODE (lst) == TRUTH_ORIF_EXPR)
tag_shortcut_cond (lst, condition_uid);
else if (TREE_CODE (lst) == COND_EXPR)
SET_EXPR_UID (lst, condition_uid);
}
}
/* Given a conditional expression EXPR with short-circuit boolean
predicates using TRUTH_ANDIF_EXPR or TRUTH_ORIF_EXPR, break the
predicate apart into the equivalent sequence of conditionals. */
predicate apart into the equivalent sequence of conditionals. CONDITION_UID
is a the tag/discriminator for this EXPR - all basic conditions in the
expression will be given the same CONDITION_UID. */
static tree
shortcut_cond_expr (tree expr)
shortcut_cond_expr (tree expr, unsigned condition_uid)
{
tree pred = TREE_OPERAND (expr, 0);
tree then_ = TREE_OPERAND (expr, 1);
@ -4296,6 +4358,8 @@ shortcut_cond_expr (tree expr)
bool then_se = then_ && TREE_SIDE_EFFECTS (then_);
bool else_se = else_ && TREE_SIDE_EFFECTS (else_);
tag_shortcut_cond (pred, condition_uid);
/* First do simple transformations. */
if (!else_se)
{
@ -4311,7 +4375,7 @@ shortcut_cond_expr (tree expr)
/* Set the source location of the && on the second 'if'. */
if (rexpr_has_location (pred))
SET_EXPR_LOCATION (expr, rexpr_location (pred));
then_ = shortcut_cond_expr (expr);
then_ = shortcut_cond_expr (expr, condition_uid);
then_se = then_ && TREE_SIDE_EFFECTS (then_);
pred = TREE_OPERAND (pred, 0);
expr = build3 (COND_EXPR, void_type_node, pred, then_, NULL_TREE);
@ -4333,7 +4397,7 @@ shortcut_cond_expr (tree expr)
/* Set the source location of the || on the second 'if'. */
if (rexpr_has_location (pred))
SET_EXPR_LOCATION (expr, rexpr_location (pred));
else_ = shortcut_cond_expr (expr);
else_ = shortcut_cond_expr (expr, condition_uid);
else_se = else_ && TREE_SIDE_EFFECTS (else_);
pred = TREE_OPERAND (pred, 0);
expr = build3 (COND_EXPR, void_type_node, pred, NULL_TREE, else_);
@ -4341,6 +4405,9 @@ shortcut_cond_expr (tree expr)
}
}
/* The expr tree should also have the expression id set. */
SET_EXPR_UID (expr, condition_uid);
/* If we're done, great. */
if (TREE_CODE (pred) != TRUTH_ANDIF_EXPR
&& TREE_CODE (pred) != TRUTH_ORIF_EXPR)
@ -4388,7 +4455,7 @@ shortcut_cond_expr (tree expr)
/* If there was nothing else in our arms, just forward the label(s). */
if (!then_se && !else_se)
return shortcut_cond_r (pred, true_label_p, false_label_p,
EXPR_LOC_OR_LOC (expr, input_location));
EXPR_LOC_OR_LOC (expr, input_location), condition_uid);
/* If our last subexpression already has a terminal label, reuse it. */
if (else_se)
@ -4420,7 +4487,8 @@ shortcut_cond_expr (tree expr)
jump_over_else = block_may_fallthru (then_);
pred = shortcut_cond_r (pred, true_label_p, false_label_p,
EXPR_LOC_OR_LOC (expr, input_location));
EXPR_LOC_OR_LOC (expr, input_location),
condition_uid);
expr = NULL;
append_to_statement_list (pred, &expr);
@ -4594,6 +4662,24 @@ generic_expr_could_trap_p (tree expr)
return false;
}
/* Associate the condition STMT with the discriminator UID. STMTs that are
broken down with ANDIF/ORIF from the same Boolean expression should be given
the same UID; 'if (a && b && c) { if (d || e) ... } ...' should yield the
{ a: 1, b: 1, c: 1, d: 2, e: 2 } when gimplification is done. This is used
for condition coverage. */
static void
gimple_associate_condition_with_expr (struct function *fn, gcond *stmt,
unsigned uid)
{
if (!condition_coverage_flag)
return;
if (!fn->cond_uids)
fn->cond_uids = new hash_map <gcond*, unsigned> ();
fn->cond_uids->put (stmt, uid);
}
/* Convert the conditional expression pointed to by EXPR_P '(p) ? a : b;'
into
@ -4696,7 +4782,7 @@ gimplify_cond_expr (tree *expr_p, gimple_seq *pre_p, fallback_t fallback)
if (TREE_CODE (TREE_OPERAND (expr, 0)) == TRUTH_ANDIF_EXPR
|| TREE_CODE (TREE_OPERAND (expr, 0)) == TRUTH_ORIF_EXPR)
{
expr = shortcut_cond_expr (expr);
expr = shortcut_cond_expr (expr, next_cond_uid ());
if (expr != *expr_p)
{
@ -4760,11 +4846,16 @@ gimplify_cond_expr (tree *expr_p, gimple_seq *pre_p, fallback_t fallback)
else
label_false = create_artificial_label (UNKNOWN_LOCATION);
unsigned cond_uid = EXPR_COND_UID (expr);
if (cond_uid == 0)
cond_uid = next_cond_uid ();
gimple_cond_get_ops_from_tree (COND_EXPR_COND (expr), &pred_code, &arm1,
&arm2);
cond_stmt = gimple_build_cond (pred_code, arm1, arm2, label_true,
label_false);
gimple_set_location (cond_stmt, EXPR_LOCATION (expr));
gimple_associate_condition_with_expr (cfun, cond_stmt, cond_uid);
copy_warning (cond_stmt, COND_EXPR_COND (expr));
gimplify_seq_add_stmt (&seq, cond_stmt);
gimple_stmt_iterator gsi = gsi_last (seq);
@ -19248,6 +19339,8 @@ gimplify_function_tree (tree fndecl)
else
push_struct_function (fndecl);
reset_cond_uid ();
/* Tentatively set PROP_gimple_lva here, and reset it in gimplify_va_arg_expr
if necessary. */
cfun->curr_properties |= PROP_gimple_lva;

View File

@ -689,7 +689,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
}
gcc_assert (gimple_in_ssa_p (DECL_STRUCT_FUNCTION (e->caller->decl))
&& gimple_in_ssa_p (DECL_STRUCT_FUNCTION (callee->decl)));
if (profile_arc_flag
if ((profile_arc_flag || condition_coverage_flag)
&& ((lookup_attribute ("no_profile_instrument_function",
DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
!= (lookup_attribute ("no_profile_instrument_function",

View File

@ -1939,7 +1939,7 @@ pass_split_functions::gate (function *)
/* When doing profile feedback, we want to execute the pass after profiling
is read. So disable one in early optimization. */
return (flag_partial_inlining
&& !profile_arc_flag && !flag_branch_probabilities);
&& !profile_arc_flag && !flag_branch_probabilities);
}
} // anon namespace

View File

@ -352,7 +352,8 @@ finish_optimization_passes (void)
gcc::dump_manager *dumps = m_ctxt->get_dumps ();
timevar_push (TV_DUMP);
if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
if (profile_arc_flag || condition_coverage_flag || flag_test_coverage
|| flag_branch_probabilities)
{
dumps->dump_start (pass_profile_1->static_pass_number, NULL);
end_branch_prob ();

View File

@ -69,6 +69,17 @@ along with GCC; see the file COPYING3. If not see
#include "profile.h"
struct condcov;
struct condcov *find_conditions (struct function*);
size_t cov_length (const struct condcov*);
array_slice<basic_block> cov_blocks (struct condcov*, size_t);
array_slice<uint64_t> cov_masks (struct condcov*, size_t);
array_slice<sbitmap> cov_maps (struct condcov* cov, size_t n);
void cov_free (struct condcov*);
size_t instrument_decisions (array_slice<basic_block>, size_t,
array_slice<sbitmap>,
array_slice<gcov_type_unsigned>);
/* Map from BBs/edges to gcov counters. */
vec<gcov_type> bb_gcov_counts;
hash_map<edge,gcov_type> *edge_gcov_counts;
@ -100,6 +111,7 @@ static int total_num_passes;
static int total_num_times_called;
static int total_hist_br_prob[20];
static int total_num_branches;
static int total_num_conds;
/* Forward declarations. */
static void find_spanning_tree (struct edge_list *);
@ -1155,6 +1167,12 @@ read_thunk_profile (struct cgraph_node *node)
the flow graph that are needed to reconstruct the dynamic behavior of the
flow graph. This data is written to the gcno file for gcov.
When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
edges in the control flow graph to track what conditions are evaluated to in
order to determine what conditions are covered and have an independent
effect on the outcome (modified condition/decision coverage). This data is
written to the gcno file for gcov.
When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
information from the gcda file containing edge count information from
previous executions of the function being compiled. In this case, the
@ -1173,6 +1191,7 @@ branch_prob (bool thunk)
struct edge_list *el;
histogram_values values = histogram_values ();
unsigned cfg_checksum, lineno_checksum;
bool output_to_file;
total_num_times_called++;
@ -1397,10 +1416,18 @@ branch_prob (bool thunk)
/* Write the data from which gcov can reconstruct the basic block
graph and function line numbers (the gcno file). */
output_to_file = false;
if (coverage_begin_function (lineno_checksum, cfg_checksum))
{
gcov_position_t offset;
/* The condition coverage needs a deeper analysis to identify expressions
of conditions, which means it is not yet ready to write to the gcno
file. It will write its entries later, but needs to know if it do it
in the first place, which is controlled by the return value of
coverage_begin_function. */
output_to_file = true;
/* Basic block flags */
offset = gcov_write_tag (GCOV_TAG_BLOCKS);
gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
@ -1514,29 +1541,65 @@ branch_prob (bool thunk)
remove_fake_edges ();
if (condition_coverage_flag || profile_arc_flag)
gimple_init_gcov_profiler ();
if (condition_coverage_flag)
{
struct condcov *cov = find_conditions (cfun);
gcc_assert (cov);
const size_t nconds = cov_length (cov);
total_num_conds += nconds;
if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
{
gcov_position_t offset {};
if (output_to_file)
offset = gcov_write_tag (GCOV_TAG_CONDS);
for (size_t i = 0; i != nconds; ++i)
{
array_slice<basic_block> expr = cov_blocks (cov, i);
array_slice<uint64_t> masks = cov_masks (cov, i);
array_slice<sbitmap> maps = cov_maps (cov, i);
gcc_assert (expr.is_valid ());
gcc_assert (masks.is_valid ());
gcc_assert (maps.is_valid ());
size_t terms = instrument_decisions (expr, i, maps, masks);
if (output_to_file)
{
gcov_write_unsigned (expr.front ()->index);
gcov_write_unsigned (terms);
}
}
if (output_to_file)
gcov_write_length (offset);
}
cov_free (cov);
}
/* For each edge not on the spanning tree, add counting code. */
if (profile_arc_flag
&& coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
{
unsigned n_instrumented;
gimple_init_gcov_profiler ();
n_instrumented = instrument_edges (el);
gcc_assert (n_instrumented == num_instrumented);
if (flag_profile_values)
instrument_values (values);
/* Commit changes done by instrumentation. */
gsi_commit_edge_inserts ();
}
free_aux_for_edges ();
values.release ();
free_edge_list (el);
/* Commit changes done by instrumentation. */
gsi_commit_edge_inserts ();
coverage_end_function (lineno_checksum, cfg_checksum);
if (flag_branch_probabilities
&& (profile_status_for_fn (cfun) == PROFILE_READ))
@ -1669,6 +1732,7 @@ init_branch_prob (void)
total_num_passes = 0;
total_num_times_called = 0;
total_num_branches = 0;
total_num_conds = 0;
for (i = 0; i < 20; i++)
total_hist_br_prob[i] = 0;
}
@ -1708,5 +1772,7 @@ end_branch_prob (void)
(total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
/ total_num_branches, 5*i, 5*i+5);
}
fprintf (dump_file, "Total number of conditions: %d\n",
total_num_conds);
}
}

View File

@ -0,0 +1,282 @@
/* { dg-options "--coverage -fcondition-coverage -std=c++11" } */
/* { dg-do run { target native } } */
#include <vector>
#include <stdexcept>
class nontrivial_destructor
{
public:
explicit nontrivial_destructor (int v) : val (v) {}
~nontrivial_destructor () {}
explicit operator bool() const { return bool(val); }
int val;
};
int identity (int x) { return x; }
int throws (int) { throw std::runtime_error("exception"); }
int
throw_if (int x)
{
if (x) /* conditions(1/2) true(0) */
/* conditions(end) */
throw std::runtime_error("exception");
return x;
}
/* Used for side effects to insert nodes in conditional bodies etc. */
int x = 0;
/* Conditionals work in the presence of non-trivial destructors. */
void
mcdc001a (int a)
{
nontrivial_destructor v (a);
if (v.val > 0) /* conditions(2/2) */
x = v.val;
else
x = -v.val;
}
/* Non-trivial destructor in-loop temporary. */
nontrivial_destructor
mcdc002a (int a, int b)
{
for (int i = 0; i < a; i++) /* conditions(2/2) */
{
nontrivial_destructor tmp (a);
if (tmp.val % b) /* conditions(2/2) */
return nontrivial_destructor (0);
x += i;
} /* conditions(suppress) */
/* conditions(end) */
return nontrivial_destructor (a * b);
}
/* Conditional in constructor. */
void
mcdc003a (int a)
{
class C
{
public:
explicit C (int e) : v (e)
{
if (e) /* conditions(1/2) false(0) */
v = x - e;
}
int v;
};
C c (a);
if (c.v > 2) /* conditions(1/2) true(0) */
/* conditions(end) */
x = c.v + a;
}
/* Conditional in destructor. */
void
mcdc004a (int a)
{
class C
{
public:
explicit C (int e) : v (e) {}
~C ()
{
if (v) /* conditions(2/2) */
x = 2 * v;
}
int v;
};
C c (a);
x = 1; // arbitrary action between ctor+dtor
}
/* Conditional in try. */
void
mcdc005a (int a)
{
try
{
if (a) /* conditions(1/2) true(0) */
/* conditions(end) */
x = 2 * identity (a);
else
x = 1;
}
catch (...)
{
x = 0;
}
}
/* Conditional in catch. */
void
mcdc006a (int a) {
try
{
throws (a);
}
catch (std::exception&)
{
if (a) /* conditions(1/2) false(0) */
/* conditions(end) */
x = identity (a);
else
x = 0;
}
}
void
mcdc006b (int a)
{
if (a) /* conditions(1/2) true(0) */
/* conditions(end) */
throws (a);
else
x = 1;
}
void
mcdc006c (int a) try
{
throws (a);
}
catch (...) {
if (a) /* conditions(2/2) */
x = 5;
}
/* Temporary with destructor as term. */
void
mcdc007a (int a, int b)
{
x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
}
void
mcdc007b (int a, int b)
{
if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
x = -1;
else
x = 1;
}
void
mcdc007c (int a, int b)
{
if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
x = -1;
else
x = 1;
}
/* Destructor with delete. */
void
mcdc008a (int a)
{
class C
{
public:
int size = 5;
int* ptr = nullptr;
explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
/* conditions(end) */
{
for (int i = 0; i < size; i++) /* conditions(2/2) */
ptr[i] = i + 1;
}
~C()
{
// delete with implicit nullptr check
delete ptr; /* conditions(1/2) false(0) */
/* conditions(end) */
}
};
C c (a);
if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
x = a;
}
/* Templates. */
template <typename T>
void
mcdc009a (T a)
{
if (a > 0) /* conditions(1/2) false(0) */
/* conditions(end) */
x += 2;
}
/* constexpr. */
/* Compile-time evaluated branches do not contribute to coverage. */
constexpr int
mcdc010a (int a, int b)
{
return a > b ? 1 : 2; /* conditions(1/2) true(0) */
/* conditions(end) */
}
/* Compile-time only evaluated functions do not show up in the compiled program
and gets no coverage at all. If this would generate output unexpectedly it
would trigger a test failure ("unexpected output"). */
constexpr int
mcdc010b (int a, int b)
{
return a > b ? 1 : 2;
}
int
main (void)
{
mcdc001a (0);
mcdc001a (1);
mcdc002a (1, 1);
mcdc002a (1, 2);
mcdc003a (1);
mcdc004a (0);
mcdc004a (1);
mcdc005a (0);
mcdc006a (1);
mcdc006b (0);
mcdc006c (0);
mcdc006c (1);
mcdc007a (0, 0);
mcdc007a (1, 1);
mcdc007b (0, 0);
mcdc007b (1, 0);
mcdc007c (0, 0);
mcdc008a (1);
mcdc009a (1);
/* Use identity () so that this is not constexpr eval'd. */
int v1 = mcdc010a (identity (2), 4);
constexpr int v2 = mcdc010a (4, 2);
constexpr int v3 = mcdc010b (2, 4);
}
/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
/* { dg-options "-fcondition-coverage -ftest-coverage -fprofile-update=atomic" } */
/* { dg-do run { target native } } */
/* Some side effect to stop branches from being pruned */
int x = 0;
void
conditions_atomic001 (int a, int b)
{
if (a || b) /* conditions(1/4) true(0) false(0 1) */
/* conditions(end) */
x = 1;
else
x = 2;
}
int main ()
{
conditions_atomic001 (0, 1);
}
/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */

View File

@ -0,0 +1,16 @@
/* { dg-options "-fcondition-coverage" } */
/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
char trim_filename_name;
int r;
void trim_filename() {
if (trim_filename_name)
r = 123;
while (trim_filename_name)
;
}
int main ()
{
}

View File

@ -0,0 +1,103 @@
/* { dg-options "-fcondition-coverage -ftest-coverage" } */
/* { dg-do run { target native } } */
#include <setjmp.h>
jmp_buf buf;
void noop () {}
int identity (int x) { return x; }
/* This function is a test to verify that the expression isolation does not
break on a CFG with the right set of complex edges. The (_ && setjmp)
created complex edges after the function calls and a circular pair of
complex edges around the setjmp call. This triggered a bug when the search
for right operands only would consider nodes dominated by the left-most
term, as this would only be the case if the complex edges were removed. (_
&& setjmp) is undefined behavior, but it does happen in the wild.
__builtin_setjmp did not trigger this, so we need setjmp from libc. */
void
setjmp001 (int a, int b, int c)
{
if (a) /* conditions(1/2) true(0) */
/* conditions(end) */
noop ();
if (b) /* conditions(1/2) false(0) */
/* conditions(end) */
noop ();
if (c && setjmp (buf)) /* conditions(1/4) true(0 1) false(1) */
/* conditions(end) */
noop ();
}
/* Adapted from freetype-2.13.0 gxvalid/gxvmod.c classic_kern_validate */
int
setjmp002 (int a)
{
int error = identity(a);
if (error) /* conditions(1/2) true(0) */
/* conditions(end) */
goto Exit;
if (a+1) /* conditions(1/2) false(0) */
/* conditions(end) */
{
noop ();
if (setjmp (buf)) /* conditions(1/2) true(0) */
/* conditions(end) */
noop ();
if (error) /* conditions(1/2) true(0) */
/* conditions(end) */
noop ();
}
error--;
Exit:
return error;
}
int
setjmp003 (int a)
{
/* || setjmp is undefined behavior, so the result here does not have to
make sense. It would be nice if the result is not something like 35/4
conditions covered. */
if (a || setjmp (buf)) /* conditions(suppress) */
/* conditions(end) */
a += 12;
return a;
}
jmp_buf dest;
int
setdest ()
{
if (setjmp (dest)) /* conditions(2/2) */
return 1;
return 2;
}
void
jump ()
{
longjmp (dest, 1);
}
int
main ()
{
setjmp001 (0, 1, 0);
setjmp002 (0);
setjmp003 (0);
setdest ();
jump ();
}
/* { dg-final { run-gcov conditions { --conditions gcov-22.c } } } */

View File

@ -0,0 +1,361 @@
/* { dg-options "-fcondition-coverage -ftest-coverage -O2 -c" } */
#include <stdint.h>
#include <limits.h>
#include <setjmp.h>
jmp_buf buf;
int id (int);
int idp (int *);
int err;
char c;
/* This becomes problematic only under optimization for the case when the
compiler cannot inline the function but have to generate a call. It is not
really interesting to run, only build. Notably, both the function calls and
the return values are important to construct a problematic graph.
This test is also a good example of where optimization makes condition
coverage unpredictable, but not unusable. If this is built without
optimization the conditions work as you would expect from reading the
source. */
/* Adapted from cpio-2.14 gnu/utmens.c lutimens (). */
int
mcdc001 (int *v)
{
int adjusted;
int adjustment_needed = 0;
int *ts = v ? &adjusted : 0; /* conditions(0/4) true(0 1) false(0 1) */
/* conditions(end) */
if (ts)
adjustment_needed = idp (ts);
if (adjustment_needed < 0)
return -1;
if (adjustment_needed) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
{
if (adjustment_needed != 3) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
return -1;
if (ts) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
return 0;
}
if (adjustment_needed && idp (&adjusted)) /* conditions(0/4) true(0 1) false(0 1) */
/* conditions(end) */
return -1;
if (adjusted) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
return idp (ts);
return -1;
}
/* This failed when the candidate set internal/contracted-past nodes were not
properly marked as reachable in the candidate reduction phase. */
/* Adapted from cpio-2.14 gnu/mktime.c mktime_internal (). */
int
mcdc002 ()
{
int a;
if (idp (&a)) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
{
if (id (a)) /* conditions(0/2) true(0/2) true(0) false(0) */
/* conditions(end) */
goto exit;
if (err) /* conditions(0/2) true(0/2) true(0) false(0) */
/* conditions(end) */
return -1;
}
exit:
return a;
}
/* Adapted from icu4c-73.1 common/ucase.cpp ucase_getCaseLocale (). */
int
mcdc003 (const char *locale)
{
/* extern, so its effect won't be optimized out. */
c = *locale++;
if (c == 'z') /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
{
return 1;
}
else if (c >= 'a') /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
{
if (id (c)) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
c = *locale++;
}
else
{
if (c == 'T')
{
if (id (c)) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
c = *locale++;
if (id (c)) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
c = *locale++;
}
/* This may or may not become a jump table. */
else if (c == 'L') /* conditions(suppress) */
/* conditions(end) */
c = *locale++;
else if (c == 'E') /* conditions(suppress) */
/* conditions(end) */
c = *locale++;
else if (c == 'N') /* conditions(suppress) */
/* conditions(end) */
c = *locale++;
else if (c == 'H') /* conditions(suppress) */
/* conditions(end) */
{
c = *locale++;
if (id (c)) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
c = *locale++;
}
}
return 1;
}
/* The || will be changed to |, so it is impractical to predict the number of
conditions. If the walk is not properly implemented this will not finish
compiling, so the actual coverage is not interesting. */
/* Adapted from icu4c-73.1 common/uresdata.cpp res_findResource (). */
int
mcdc004 (int r, char* path, char* key)
{
char *idcc (char *, char);
#define is_kind1(type) ((type) == 23 || (type) == 14 || (type == 115))
#define is_kind2(type) ((type) == 16 || (type) == 77 || (type == 118))
#define is_kind12(type) (is_kind1 ((type)) || is_kind2 ((type)))
char *nextSepP = path;
int t1 = r;
int type = id (t1);
if (!is_kind12 (type)) /* conditions(suppress) */
/* conditions(end) */
return -1;
while (*path && t1 != -1 && is_kind12 (type)) /* conditions(suppress) */
/* conditions(end) */
{
nextSepP = idcc(path, '/');
if(nextSepP == path) /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
return -1;
if (*nextSepP == 'a') /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
*key = *path;
else if (*nextSepP == 'b') /* conditions(0/2) true(0) false(0) */
/* conditions(end) */
*key = 0;
type = t1;
}
return t1;
}
/* Adapted from jxl 0.8.2 lib/extras/dec/apng.cc processing_start ().
This created a graph where depth-first traversal of the CFG would not
process nodes in the wrong order (the extra control inserted from setjmp
created a path of complexes from root to !b without going through !a).
This only happened under optimization. */
int
mcdc005 (int a, int b)
{
a = id (a);
b = id (b);
/* The a || b gets transformed to a | b, then fused with setjmp because
they both have the same return value. */
if (a || b) /* conditions(0/4) true(0 1) false(0 1) */
/* conditions(end) */
return 1;
else
a += 1;
if (setjmp (buf))
return 1;
return a;
}
/* Adapted from cpio-2.14 gnu/quotearg.c quotearg_buffer_restyled. The ifs in
the cases (with fallthrough) re-use the same value which under optimization
causes path reuse which must be sorted out. */
int
mcdc006 (int quoting_style, int elide, int *buffer)
{
int backslash = 0;
switch (quoting_style)
{
case 1:
if (!elide)
backslash = 1;
case 2:
if (!elide)
if (buffer)
*buffer = '"';
}
if (quoting_style == 2 && backslash)
quoting_style = 1;
return 1;
}
/* Adapted from pcre2-10.42 pcre2_compile.c pcre2_compile. If SSA nodes are
created at the wrong place in the block it will fail flow analysis (because
the label is in the middle of block), caused by the final break in this
case. */
void
mcdc007 (int options, int *pso, int len)
{
if (options == 5)
return;
while (options--)
{
int i;
for (i = 0; i < len; i++)
{
int *p = pso + i;
int skipatstart = *p + 2;
if (skipatstart) {
switch(*p)
{
case 1:
*p |= *p + 1;
break;
case 2:
skipatstart += *p - skipatstart;
break;
}
break;
}
}
if (i >= len) break;
}
}
/* Adapted from alsa-lib 1.2.8 pcm/pcm.c snd_pcm_chmap_print. */
int
mcdc008 (int *map, unsigned maxlen, int *buf)
{
unsigned int len = 0;
for (unsigned i = 0; i < *map; i++) {
unsigned int p = map[i] & 0xF;
if (i > 0) {
if (len >= maxlen)
return -1;
}
if (map[i] & 0xFF)
len += idp (buf + len);
else {
len += idp (buf);
}
if (map[i] & 0xFF00) {
len += idp (buf + len);
if (len >= maxlen)
return -1;
}
}
return len;
}
/* Adapted from cpio-2.14 gnu/mktime.c mktime_internal (). The combination of
goto, automatic variables, and the ternary causes the post dominator of the
highest topological ordered node not to be the common post dominator of the
expression as a whole. */
int
mcdc009 (int *tp, int t, int isdst)
{
int t0 = tp[0];
int t1 = tp[1];
int t2 = tp[2];
if (t0 < 0 || (isdst < 0 ? t1 : (isdst != 0)))
goto offset_found;
if (t == 0)
return -1;
t1 = t2;
offset_found:
return t;
}
/* Adapted from Berkeley db 4.8.30 rep/rep_elect.c __rep_cmp_vote. This
particular combination of fallthrough and folding creates a path into the
the inner if () that does not go through the first basic condition. */
void
mcdc010 (int cmp, int *rep, int sites, int priority, int flags)
{
if (sites > 1 && (priority != 0 || (flags & 0xFF)))
{
if ( (priority != 0 && *rep == 0)
|| (((priority == 0 && *rep == 0)
|| (priority != 0 && *rep != 0)) && cmp > 0))
{
*rep = cmp;
}
}
}
/* For not sufficiently protected back edges this would create an infinite
loop. */
void
mcdc011 (int a, int b)
{
if (a && id (b))
for (;;) {}
id (a+1);
}
/* Adapted from alsa-1.2.8 tlv.c get_tlv_info (). Under optimization, the
conditions may be replaced with min (). */
int
mcdc012 (int x, int y)
{
int err;
err = id (x);
if (err < 0)
return err;
err = id (y);
if (err < 0)
return err;
return 0;
}
/* Adapted from alsa-1.2.8 control.c snd_ctl_elem_id_compare_numid (). This
test is probably not so accurate on targets where int == int64. Under
optimization, the conditions may be replaced with min/max. */
int
mcdc013 (const int64_t *id1, const int64_t *id2)
{
int64_t d;
d = *id1 - *id2;
if (d & 0xFF)
{
if (d > INT_MAX)
d = INT_MAX;
else if (d < INT_MIN)
d = INT_MIN;
}
return d;
}

View File

@ -174,6 +174,252 @@ proc verify-branches { testname testcase file } {
return $failed
}
#
# verify-conditions -- check that conditions are checked as expected
#
# TESTNAME is the name of the test, including unique flags.
# TESTCASE is the name of the test file.
# FILE is the name of the gcov output file.
#
# Checks are based on comments in the source file. Condition coverage comes
# with with two types of output, a summary and a list of the uncovered
# conditions. Both must be checked to pass the test
#
# To check for conditions, add a comment the line of a conditional:
# /* conditions(n/m) true(0 1) false(1) */
#
# where n/m are the covered and total conditions in the expression. The true()
# and false() take the indices expected *not* covered.
#
# This means that all coverage statements should have been seen:
# /* conditions(end) */
#
# If all conditions are covered i.e. n == m, then conditions(end) can be
# omitted. If either true() or false() are empty they can be omitted too.
#
# In some very specific cases there is a need to match multiple conditions on
# the same line, for example if (a && fn (b || c) && d), which is interpreted
# roughly as tmp _bc = b || c; if (a && _bc && d). The argument to fn is
# considered its own expression and its coverage report will be written on the
# same line. For these cases, use conditions(n/m; n/m;...) true(0 1;;...)
# where ; marks the end of the list element where the ith list matches the ith
# expression. The true()/false() matchers can be omitted if no expression
# expects them, otherwise use the empty list if all true/false outcomes are
# covered.
#
# C++ can insert conditionals in the CFG that are not present in source code.
# These must be manually suppressed since unexpected and unhandled conditions
# are an error (to help combat regressions). Output can be suppressed with
# conditions(suppress) and conditions(end). suppress should usually be on a
# closing brace.
#
# Some expressions, when using unnamed temporaries as operands, will have
# destructors in expressions. The coverage of the destructor will be reported
# on the same line as the expression itself, but suppress() would also swallow
# the expected tested-for messages. To handle these, use the destructor() [1]
# which will suppress everything from and including the second "conditions
# covered".
#
# [1] it is important that the destructor() is *on the same line* as the
# conditions(m/n)
proc verify-conditions { testname testcase file } {
set failed 0
set suppress 0
set destructor 0
set should ""
set shouldt ""
set shouldf ""
set shouldall ""
set fd [open $file r]
set lineno 0
set checks [list]
set keywords {"end" "suppress"}
while {[gets $fd line] >= 0} {
regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all lineno
set prefix "$testname line $lineno"
if {![regexp "condition" $line]} {
continue
}
# Missing coverage for both true and false will cause a failure, but
# only count it once for the report.
set ok 1
if [regexp {conditions *\([0-9a-z/; ]+\)} "$line" all] {
# *Very* coarse sanity check: conditions() should either be a
# keyword or n/m, anything else means a buggy test case. end is
# optional for cases where all conditions are covered, since it
# only expects a single line of output.
regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e
if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
incr failed
continue
}
# Any keyword means a new context. Set the error flag if not all
# expected output has been seen, and reset the state.
if {[llength $shouldt] != 0} {
fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
set ok 0
}
if {[llength $shouldf] != 0} {
fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
set ok 0
}
if {$shouldall ne ""} {
fail "$prefix: coverage summary not found; expected $shouldall"
set ok 0
}
if {[llength $checks] != 0} {
set missing [llength checks]
fail "$prefix: expected $missing more conditions"
set ok 0
}
set suppress 0
set destructor 0
set setup 0
set checks [list]
if [regexp {destructor\(\)} "$line"] {
set destructor 1
}
# Find the expressions on this line. There may be more, to support
# constructs like (a && fn (b && c) && d).
# The match produces lists like [conditions(n/m) n m]
set argconds ""
set argtrue ""
set argfalse ""
regexp {conditions *\(([0-9 /;]+)\)} $line _ argconds
regexp {true *\(([0-9 ;]+)\)} $line _ argtrue
regexp {false *\(([0-9 ;]+)\)} $line _ argfalse
set condv [split $argconds ";"]
set truev [split $argtrue ";"]
set falsev [split $argfalse ";"]
set ncases [llength $condv]
for {set i 0} {$i < $ncases} {incr i} {
set summary [lindex $condv $i]
set n [lindex [split $summary "/"] 0]
set m [lindex [split $summary "/"] 1]
set newt [lindex $truev $i]
set newf [lindex $falsev $i]
# Sanity check - if the true() and false() vectors should have
# m-n elements to cover all uncovered conditions. Because of
# masking it can sometimes be surprising what terms are
# independent, so this makes for more robust test at the cost
# of being slightly more annoying to write.
set nterms [expr [llength $newt] + [llength $newf]]
set nexpected [expr {$m - $n}]
if {$nterms != $nexpected} {
fail "$prefix: expected $nexpected uncovered terms; got $nterms"
set ok 0
}
set shouldall $e
set should ""
set shouldt $newt
set shouldf $newf
set shouldall [regsub -all { } "$n/$m" ""]
lappend checks [list $should $shouldt $shouldf $shouldall $newt $newf]
}
if {[llength $checks] > 0} {
# no-op - the stack of checks to do is set up
} elseif {$e == "end"} {
# no-op - state should already been reset, and errors flagged
} elseif {$e == "suppress"} {
set suppress 1
} else {
# this should be unreachable,
fail "$prefix: unhandled control ($e), should be unreachable"
set ok 0
}
} elseif {$suppress == 1} {
# ignore everything in a suppress block. C++ especially can insert
# conditionals in exceptions and destructors which would otherwise
# be considered unhandled.
continue
} elseif [regexp {condition +(\d+) not covered \((.*)\)} "$line" all cond condv] {
foreach v {true false} {
if [regexp $v $condv] {
if {"$v" == "true"} {
set should shouldt
} else {
set should shouldf
}
set i [lsearch [set $should] $cond]
if {$i != -1} {
set $should [lreplace [set $should] $i $i]
} else {
fail "$prefix: unexpected uncovered term $cond ($v)"
set ok 0
}
}
}
} elseif [regexp {condition outcomes covered (\d+/\d+)} "$line" all cond] {
# the destructor-generated "conditions covered" lines will be
# written after all expression-related output. Handle these by
# turning on suppression if the destructor-suppression is
# requested.
if {$shouldall == "" && $destructor == 1} {
set suppress 1
continue
}
if {[llength $checks] == 0} {
fail "$prefix: unexpected summary $cond"
set ok 0
} else {
# Report any missing conditions from the previous set if this
# is not the first condition block
if {$setup == 1} {
if {[llength $shouldt] != 0} {
fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
set ok 0
}
if {[llength $shouldf] != 0} {
fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
set ok 0
}
if {$shouldall ne ""} {
fail "$prefix: coverage summary not found; expected $shouldall"
set ok 0
}
}
set setup 1
set current [lindex $checks 0]
set checks [lreplace $checks 0 0]
set should [lindex $current 0]
set shouldt [lindex $current 1]
set shouldf [lindex $current 2]
set shouldall [lindex $current 3]
set newt [lindex $current 4]
set newf [lindex $current 5]
if {$cond == $shouldall} {
set shouldall ""
} else {
fail "$prefix: unexpected summary - expected $shouldall, got $cond"
set ok 0
}
}
}
if {$ok != 1} {
incr failed
}
}
close $fd
return $failed
}
#
# verify-calls -- check that call return percentages are as expected
#
@ -321,6 +567,7 @@ proc run-gcov { args } {
set gcov_args ""
set gcov_verify_calls 0
set gcov_verify_branches 0
set gcov_verify_conditions 0
set gcov_verify_lines 1
set gcov_verify_intermediate 0
set gcov_remove_gcda 0
@ -331,10 +578,13 @@ proc run-gcov { args } {
set gcov_verify_calls 1
} elseif { $a == "branches" } {
set gcov_verify_branches 1
} elseif { $a == "conditions" } {
set gcov_verify_conditions 1
} 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
} elseif { $a == "remove-gcda" } {
set gcov_remove_gcda 1
@ -404,6 +654,11 @@ proc run-gcov { args } {
} else {
set bfailed 0
}
if { $gcov_verify_conditions } {
set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
} else {
set cdfailed 0
}
if { $gcov_verify_calls } {
set cfailed [verify-calls $testname $testcase $testcase.gcov]
} else {
@ -418,12 +673,12 @@ proc run-gcov { args } {
# Report whether the gcov test passed or failed. If there were
# multiple failures then the message is a summary.
set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
if { $xfailed } {
setup_xfail "*-*-*"
}
if { $tfailed > 0 } {
fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $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"
if { $xfailed } {
clean-gcov $testcase
}

View File

@ -1600,6 +1600,9 @@ enum omp_clause_linear_kind
struct GTY(()) tree_exp {
struct tree_typed typed;
location_t locus;
/* Discriminator for basic conditions in a Boolean expressions. Trees that
are operands of the same Boolean expression should have the same uid. */
unsigned condition_uid;
tree GTY ((length ("TREE_OPERAND_LENGTH ((tree)&%h)"))) operands[1];
};

File diff suppressed because it is too large Load Diff

View File

@ -1358,6 +1358,10 @@ class auto_suppress_location_wrappers
~auto_suppress_location_wrappers () { --suppress_location_wrappers; }
};
/* COND_EXPR identificer/discriminator accessors. */
#define SET_EXPR_UID(t, v) EXPR_CHECK ((t))->exp.condition_uid = (v)
#define EXPR_COND_UID(t) EXPR_CHECK ((t))->exp.condition_uid
/* In a TARGET_EXPR node. */
#define TARGET_EXPR_SLOT(NODE) TREE_OPERAND_CHECK_CODE (NODE, TARGET_EXPR, 0)
#define TARGET_EXPR_INITIAL(NODE) TREE_OPERAND_CHECK_CODE (NODE, TARGET_EXPR, 1)

View File

@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters __attribute__ ((unused)),
unsigned n_counters __attribute__ ((unused))) {}
#endif
#ifdef L_gcov_merge_ior
void __gcov_merge_ior (gcov_type *counters __attribute__ ((unused)),
unsigned n_counters __attribute__ ((unused))) {}
#endif
#ifdef L_gcov_merge_topn
void __gcov_merge_topn (gcov_type *counters __attribute__ ((unused)),
unsigned n_counters __attribute__ ((unused))) {}