mirror of
git://gcc.gnu.org/git/gcc.git
synced 2024-11-28 01:10:21 +08:00
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:
parent
b7bd2ec73d
commit
08a5233180
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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.
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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}}}";
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
209
gcc/gcov.cc
209
gcc/gcov.cc
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
123
gcc/gimplify.cc
123
gcc/gimplify.cc
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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 ();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
282
gcc/testsuite/g++.dg/gcov/gcov-18.C
Normal file
282
gcc/testsuite/g++.dg/gcov/gcov-18.C
Normal 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 } } } */
|
1737
gcc/testsuite/gcc.misc-tests/gcov-19.c
Normal file
1737
gcc/testsuite/gcc.misc-tests/gcov-19.c
Normal file
File diff suppressed because it is too large
Load Diff
22
gcc/testsuite/gcc.misc-tests/gcov-20.c
Normal file
22
gcc/testsuite/gcc.misc-tests/gcov-20.c
Normal 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 } } } */
|
16
gcc/testsuite/gcc.misc-tests/gcov-21.c
Normal file
16
gcc/testsuite/gcc.misc-tests/gcov-21.c
Normal 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 ()
|
||||
{
|
||||
}
|
103
gcc/testsuite/gcc.misc-tests/gcov-22.c
Normal file
103
gcc/testsuite/gcc.misc-tests/gcov-22.c
Normal 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 } } } */
|
361
gcc/testsuite/gcc.misc-tests/gcov-23.c
Normal file
361
gcc/testsuite/gcc.misc-tests/gcov-23.c
Normal 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;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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];
|
||||
};
|
||||
|
||||
|
1058
gcc/tree-profile.cc
1058
gcc/tree-profile.cc
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
@ -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))) {}
|
||||
|
Loading…
Reference in New Issue
Block a user