Add a new attribute, [[gnu::disable_coverage]] for statements which
disables the coverage report for that statement. This is quite useful
for things like unreachable states which would score worse on coverage
despite being better code.
A motivating example from the wild is contracts in debug builds. Let's
assume we have a precondition assert macro:
#define REQUIRE(pred) assert (((void)"Precondition violated", pred))
int div (int x, int y)
{
REQUIRE (y != 0);
return x / y;
}
The REQUIRE/assert introduces a branch in code that is otherwise a
straight line, and if the function is never called with y = 0 (which
is undefined), the assert-failure branch will not be covered, and
pollutes the coverage report. GCC and gcov can already filter out on
the function and level (e.g. -fprofile-filter-files=regex), but that
would not support this use case. The disable_coverage attribute is a
high resolution tool for very specific bits of code so that shouldn't
be counted towards coverage.
We can change the REQUIRE macro to either of these to make the problem
go away.
#define REQUIRE(pred) \
__attribute__((disable_coverage)) \
assert (((void)"Precondition violated", pred))
#define REQUIRE(pred) \
[[gnu::disable_coverage]] \
assert (((void)"Precondition violated", pred))
The attribute works on pretty much any statement, including if/else,
for/while/do, compound statements, and returns. When applied to a
compound statement or a loop, it will disable coverage for every
statement inside that block.
[[gnu::disable_coverage]]
if (cond)
{
/* Coverage disabled. */
}
else
{
/* Also here. */
}
--
DEMO
gcc div.c -o div --coverage && gcov -fbc div
Without disable_coverage. The fallthrough branch is taken 0 times, and
we have 50% branch coverage.
Function 'div'
Lines executed:100.00% of 3
Branches executed:100.00% of 2
Taken at least once:50.00% of 2
No calls
-: 0:Source:div.c
-: 0:Graph:div.gcno
-: 0:Data:div.gcda
-: 0:Runs:1
-: 1:#include <assert.h>
-: 2:
-: 3:#define REQUIRE(pred, msg) \
-: 4: assert (((void)msg, pred))
-: 5:
function div called 1 returned 100% blocks executed 75%
1: 6:double div (double x, double y)
-: 7:{
-: 8: /* gcov reports that this function has no branches. */
1*: 9: REQUIRE (y != 0, "division by zero");
branch 0 taken 0 (fallthrough)
branch 1 taken 1
call 2 never executed
1: 10: return x / y;
-: 11:}
-: 12:
function main called 1 returned 100% blocks executed 100%
1: 13:int main() {
1: 14: return div(0, 5);
call 0 returned 1
-: 15:}
With disable_coverage. The branch is effectively gone.
Function 'div'
Lines executed:100.00% of 2 (1 of 3 ignored)
Branches executed:0.00% of 0 (2 of 2 ignored)
Taken at least once:0.00% of 0
Calls executed:0.00% of 0 (1 of 1 ignored)
-: 0:Source:div.c
-: 0:Graph:div.gcno
-: 0:Data:div.gcda
-: 0:Runs:2
-: 1:#include <assert.h>
-: 2:
-: 3:#define REQUIRE(pred, msg) \
-: 4: __attribute__((disable_coverage)) assert (((void)msg, pred))
-: 5:
function div called 2 returned 100% blocks executed 80%
2: 6:double div (double x, double y)
-: 7:{
-: 8: /* gcov reports that this function has no branches. */
#: 9: REQUIRE (y != 0, "division by zero");
2: 10: return x / y;
-: 11:}
-: 12:
function main called 2 returned 100% blocks executed 100%
2: 13:int main() {
2: 14: return div(0, 5);
call 0 returned 2
-: 15:}
--
IMPLEMENTATION DETAILS
[[gnu::disable_coverage]] works by inserting two internal function
calls before and after the statement, essentially translating:
[[gnu::disable_coverage]]
if (x) { xthen (); } else { xelse (); }
into
.disable_coverage (1)
if (x) { xthen (); }
else { xelse (); }
.enable_coverage (1)
In the CFG construction pass, these calls works like scope push/pop
[1]. Basic blocks are recorded as disabled when they are created,
which happens before edge creation and before noreturn functions like
exit() would prune blocks. I tried computing affected blocks from
just graph analysis, but with arbitrary gotos into scopes, out of
scopes, and gotos chained into exits there is probably no way of
reliably doing this after the CFG is constructed.
This relies the GIMPLE sequence being faithful to the tree sequence
emitted by the parser, so that the .disable/.enable calls come just
before/after the sequence of the statement. This happens to not be
the case for the C++ catch handlers.
[[gnu::disable_coverage]]
try { fn (); }
catch (...) { handle(); }
becomes
.disable_coverage (2)
try { fn (); }
catch (...) { handle(); }
.enable_coverage (2)
which in GIMPLE becomes
.disable_coverage ()
try { fn (); }
catch { goto L0; }
// more code
L0:
handle ();
// more code
We can fix this at parse time by checking the generated trees for
handlers and inject a .disable/.enable pair there, roughly
corresponding to:
.disable_coverage (2)
try { fn (); }
catch (...) {
.disable_coverage (2);
handle();
.enable_coverage (2);
}
.enable_coverage (2)
--
GCOV
The coverage instrumentation is not actually changed in any meaningful
way, disabling coverage is about interpretation. Rather than changing
the instrumentation, we add a new IGNORE record with metadata which
tells gcov which blocks should be blacklisted from the coverage
report. When gcov prints its report it checks blocks against the
IGNORE record, and by extension arc, condition, and path, against the
blacklist and decides whether or not to include it.
Prime paths turned out to be tricky. If we ignore out a subsequence we
want to unify the paths that used to differ but now don't care about
the path through the ignored subgraph, and either may have been
covered.
--
Bootstrapped and reg-tested on linux-x86_64.
[1] I implemented these as two internal functions, but they could also
be tree-codes (like debug stmt) or a single call with args. I am
unaware of any drawbacks.
gcc/c-family/ChangeLog:
* c-common.cc (next_disable_coverage_uid): New function.
(build_call_disable_coverage): Likewise.
(build_call_enable_coverage): Likewise.
(handler_stmt_p): Likewise.
(collect_handler_trees): Likewise.
(disable_coverage_in_catch_handlers): Likewise.
* c-common.h (build_call_enable_coverage): New declaration.
(build_call_disable_coverage): Likewise.
(disable_coverage_in_catch_handlers): Likewise.
(next_disable_coverage_uid): Likewise.
gcc/c/ChangeLog:
* c-parser.cc (coverage_instrumentation_p): New forward decl.
(struct c_parser_auto_disable_coverage): New.
(struct attr_state): Add field disable_coverage_p.
(c_parser_handle_disable_coverage): New function.
(c_parser_declaration_or_fndef): Handle disable_coverage
attribute.
(c_parser_compound_statement_nostart): Likewise.
(c_parser_all_labels): Likewise.
(c_parser_statement_after_labels): Likewise.
gcc/cp/ChangeLog:
* parser.cc (coverage_instrumentation_p): New forward decl.
(cp_parser_handle_disable_coverage): New function.
(struct cp_auto_disable_coverage): New.
(cp_parser_statement): Handle disable_coverage attribute.
(cp_parser_simple_declaration): Likewise.
gcc/ChangeLog:
* doc/extend.texi: Add section on disable_coverage.
* function.cc (free_after_compilation): Free
disabled_coverage_blocks.
* function.h (struct function): Add disabled_coverage_blocks.
* gcov-dump.cc (tag_ignore): New function.
* gcov-io.h (GCOV_TAG_IGNORE): New.
(GCOV_TAG_IGNORE_LENGTH): New.
(GCOV_TAG_IGNORE_NUM): New.
* gcov.cc (struct arc_info): Add field ignored.
(struct coverage_info): Add fields lines_ignored,
branches_ingored, conditions_ignored, calls_ignored.
(struct path_info): Add fields, functions for ignored paths.
(block_info::block_info): Initialize ignored.
(line_info::line_info): Initialize ignored.
(struct coverage_info): Add field ignored.
(executed_summary): Add ignored argument.
(tombstone_p): New function.
(remove_tombstones): Likewise.
(tombstone_subsequence_p): Likewise.
(subsumed_by_any_p): Likewise.
(path_info::ignore_blocks): New function.
(find_arc): Return NULL if arc cannot be found.
(main): Pass total_ignored to executed_summary.
(output_intermediate_json_line): Skip ignored.
(json_set_prime_path_coverage): Include ignored counts.
(output_json_intermediate_file): Likewise.
(process_all_functions): Record block, arc ignored status.
(generate_results): Count total_ignored.
(read_graph_file): Read IGNORE tag.
(add_branch_counts): Count ignored.
(add_condition_counts): Likewise.
(add_path_counts): Likewise.
(function_summary): Print info on ignored blocks.
(file_summary): Count ignored.
(add_line_counts): Likewise.
(accumulate_line_info): Likewise.
(accumulate_line_counts): Likewise.
(output_conditions): Skip ignored.
(output_branch_count): Likewise.
(print_prime_path_lines): Annotate ignored arcs.
(print_prime_path_source): Likewise.
(output_path_coverage): Print info on ignored paths.
(output_line_beginning): Prefix ignored lines with '#'.
(output_line_details): Pass ignored.
(output_lines): Likewise.
* internal-fn.cc (expand_DISABLE_COVERAGE): New function.
(expand_ENABLE_COVERAGE): Likewise.
* internal-fn.def (DISABLE_COVERAGE): Likewise.
(ENABLE_COVERAGE): Likewise.
* profile.cc (disable_coverage): Likewise.
(coverage_disabled_p): Likewise.
(any_block_disabled_p): Likewise.
(branch_prob): Write IGNORE record.
* tree-cfg.cc (coverage_disabled_p): Forward decl.
(disable_coverage): Likewise.
(make_blocks_1): Filter out .disable/.enable coverage calls.
(make_blocks): Insert GIMPLE_NOP if the first statement is a
disable/enable coverage call.
(gimple_can_merge_blocks_p): Don't merge coverage-disabled
blocks with coverage-enabled blocks:
(stmt_starts_bb_p): .disable/.enable calls start blocks.
(gimple_split_edge): Propagate coverage-disabled on block
splits.
(gimple_split_block): Likewise.
(gimple_duplicate_bb): Likewise.
(insert_cond_bb): Likewise.
gcc/testsuite/ChangeLog:
* g++.dg/gcov/gcov-24.C: New test.
* g++.dg/gcov/gcov-25.C: New test.
* g++.dg/gcov/gcov-26.C: New test.
* g++.dg/gcov/gcov-27.C: New test.
* gcc.misc-tests/gcov-35.c: New test.
* gcc.misc-tests/gcov-36.c: New test.
* gcc.misc-tests/gcov-37.c: New test.
* gcc.misc-tests/gcov-38.c: New test.
* gcc.misc-tests/gcov-39.c: New test.
---
gcc/c-family/c-common.cc | 78 +++
gcc/c-family/c-common.h | 6 +
gcc/c/c-parser.cc | 158 ++++-
gcc/cp/parser.cc | 72 +++
gcc/doc/extend.texi | 78 +++
gcc/function.cc | 1 +
gcc/function.h | 4 +
gcc/gcov-dump.cc | 22 +
gcc/gcov-io.h | 3 +
gcc/gcov.cc | 502 +++++++++++++--
gcc/internal-fn.cc | 16 +
gcc/internal-fn.def | 4 +
gcc/profile.cc | 37 ++
gcc/testsuite/g++.dg/gcov/gcov-24.C | 801 ++++++++++++++++++++++++
gcc/testsuite/g++.dg/gcov/gcov-25.C | 98 +++
gcc/testsuite/g++.dg/gcov/gcov-26.C | 325 ++++++++++
gcc/testsuite/g++.dg/gcov/gcov-27.C | 32 +
gcc/testsuite/gcc.misc-tests/gcov-35.c | 834 +++++++++++++++++++++++++
gcc/testsuite/gcc.misc-tests/gcov-36.c | 98 +++
gcc/testsuite/gcc.misc-tests/gcov-37.c | 171 +++++
gcc/testsuite/gcc.misc-tests/gcov-38.c | 96 +++
gcc/testsuite/gcc.misc-tests/gcov-39.c | 29 +
gcc/tree-cfg.cc | 85 +++
23 files changed, 3487 insertions(+), 63 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-24.C
create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-25.C
create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-26.C
create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-27.C
create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-35.c
create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-36.c
create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-37.c
create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-38.c
create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-39.c
diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index bf1cdaf34d3..d26ab4841ef 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -10668,4 +10668,82 @@ c_hardbool_type_attr_1 (tree type, tree *false_value,
tree *true_value)
return attr;
}
+/* Return a fresh uid for an .DISABLE/.ENABLE pair. Used for
+ [[gnu::disable_coverage]]. */
+int
+next_disable_coverage_uid ()
+{
+ static int uid = 1;
+ return uid++;
+}
+
+/* Build a call to .DISABLE_COVERAGE. This call is emitted by
+ __attribute__((disable_coverage)) stmt; and makes stmt be ignored in the
+ gcov coverage reports. It should must be paired with calls to
+ .ENABLE_COVERAGE with a matching UID (see build_call_enable_coverage). */
+tree
+build_call_disable_coverage (int uid)
+{
+ return build_call_expr_internal_loc (UNKNOWN_LOCATION, IFN_DISABLE_COVERAGE,
+ void_type_node, 1,
+ build_int_cst (integer_type_node, uid));
+}
+
+/* Build a call to .ENABLE_COVERAGE. The UID matches this to the sibling
+ build_call_disable_coverage call. */
+tree
+build_call_enable_coverage (int uid)
+{
+ return build_call_expr_internal_loc (UNKNOWN_LOCATION, IFN_ENABLE_COVERAGE,
+ void_type_node, 1,
+ build_int_cst (integer_type_node, uid));
+}
+
+/* Check if T is a handler type statement; catch (...) handler. */
+static bool
+handler_stmt_p (tree t)
+{
+ tree_code c = TREE_CODE (t);
+ return c == HANDLER || c == CATCH_EXPR || c == TRY_CATCH_EXPR;
+}
+
+/* Callback for disable_coverage_in_catch_handlers. TP is the current tree.
+ Records all subtrees with a handler type in DATA. */
+static tree
+collect_handler_trees (tree *tp, int *, void *data)
+{
+ vec<tree> *stmts = (vec<tree>*)data;
+ if (handler_stmt_p (*tp))
+ stmts->safe_push (*tp);
+ return NULL_TREE;
+}
+
+/* Find catch handlers and insert .DISABLE/.ENABLE calls before- and after the
+ handler body.
+
+ It is usually sufficient to insert a pair of.DISABLE and .ENABLE calls
+ before and after a statement statement annotated with
+ [[gnu::disable_coverage]], but the handler code may be put at the end of the
+ stmt sequence. We know that the full try/catch has been annotated already
+ so we work around it by inserting extra .DISABLE/.ENABLE in the handler
+ body. */
+void
+disable_coverage_in_catch_handlers (tree root, int uid)
+{
+ auto_vec<tree, 4> stmts;
+ walk_tree (&root, collect_handler_trees, &stmts, nullptr);
+ for (tree stmt : stmts)
+ {
+ gcc_assert (handler_stmt_p (stmt));
+ tree disable = build_call_disable_coverage (uid);
+ tree enable = build_call_enable_coverage (uid);
+ tree stmtlist = nullptr;
+ /* Operand 1 is the block for all the catch (...) handler; types. */
+ append_to_statement_list_force (disable, &stmtlist);
+ append_to_statement_list_force (TREE_OPERAND (stmt, 1), &stmtlist);
+ append_to_statement_list_force (enable, &stmtlist);
+ TREE_OPERAND (stmt, 1) = stmtlist;
+ }
+}
+
#include "gt-c-family-c-common.h"
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 537b9acb068..2c47c97609c 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1115,6 +1115,12 @@ extern tree resolve_overloaded_builtin (location_t,
tree, vec<tree, va_gc> *,
extern tree finish_label_address_expr (tree, location_t);
+/* For implementing [[gnu::disable_coverage]]. */
+extern tree build_call_enable_coverage (int);
+extern tree build_call_disable_coverage (int);
+extern void disable_coverage_in_catch_handlers (tree, int);
+extern int next_disable_coverage_uid ();
+
/* Same function prototype, but the C and C++ front ends have
different implementations. Used in c-common.cc. */
extern tree lookup_label (tree);
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 931ae88cd2f..4b8620925a6 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -87,6 +87,73 @@ along with GCC; see the file COPYING3. If not see
vec<tree> incomplete_record_decls;
+/* Check if any coverage is enabled; if not, __attribute__((disable_coverage))
+ is a no-op. */
+bool coverage_instrumentation_p ();
+
+namespace {
+/* Scope-based helper for inserting .DISABLE/.ENABLE pairs on declarations with
+ initializers. Inserting the pair is postponed until an initializer is found
+ on the declaration (and disable () is called) to not pollute in case there
+ is no initializer. When the declaration has an initializer we want to
+ insert a .DISABLE before the declaration, and an .ENABLE after all
+ initializers, a nice fit for the destructor. */
+struct c_parser_auto_disable_coverage
+{
+ int uid;
+ tree_stmt_iterator tsi;
+ bool disable_coverage_p;
+
+ /* Record where we are in CUR_STMT_LIST so we can insert the .DISABLE call
+ here later and know where to start looking for catch handlers. If
+ DISABLE_P is false, the handle will do nothing. */
+ c_parser_auto_disable_coverage (bool disable_p) : uid (-1), tsi ({}),
+ disable_coverage_p (coverage_instrumentation_p () && disable_p)
+ {
+ /* cur_stmt_list may be NULL if this is the first declaration or statement
+ in the function. */
+ if (disable_coverage_p && cur_stmt_list)
+ tsi = tsi_last (cur_stmt_list);
+ }
+
+ /* Emit the .DISABLE call if DISABLE_P was true in the constructor. This
+ should be called when an initializer is parsed. The .DISABLE call is
+ inserted into the stmt list as if it was inserted when the handle was
+ constructed. */
+ void disable ()
+ {
+ if (disable_coverage_p && uid == -1)
+ {
+ uid = next_disable_coverage_uid ();
+ tree stmt = build_call_disable_coverage (uid);
+ if (tsi_end_p (tsi))
+ {
+ gcc_assert (cur_stmt_list);
+ tsi = tsi_start (cur_stmt_list);
+ tsi_link_before (&tsi, stmt, TSI_NEW_STMT);
+ }
+ else
+ tsi_link_after (&tsi, stmt, TSI_NEW_STMT);
+ }
+ }
+
+ /* Insert the closing .ENABLE call. Walk the trees created between
+ construction and destruction, and insert .DISABLE/.ENABLE pairs in freshly
+ created catch handlers. */
+ ~c_parser_auto_disable_coverage ()
+ {
+ if (disable_coverage_p && uid != -1)
+ {
+ for (tsi_next (&tsi); !tsi_end_p (tsi); tsi_next (&tsi))
+ disable_coverage_in_catch_handlers (*tsi, uid);
+ add_stmt (build_call_enable_coverage (uid));
+ }
+ }
+};
+
+}
+
+
void
set_c_expr_source_range (c_expr *expr,
location_t start, location_t finish)
@@ -1791,6 +1858,9 @@ struct attr_state
{
/* True if we parsed a musttail attribute for return. */
bool musttail_p;
+
+ /* True if we parsed a disable_coverage attribute for the statement. */
+ bool disable_coverage_p;
};
static bool c_parser_nth_token_starts_std_attributes (c_parser *,
@@ -1951,6 +2021,7 @@ static bool c_parser_objc_diagnose_bad_element_prefix
(c_parser *, struct c_declspecs *);
static location_t c_parser_parse_rtl_body (c_parser *, char *);
static tree c_parser_handle_musttail (c_parser *, tree, attr_state &);
+static tree c_parser_handle_disable_coverage (c_parser *, tree, attr_state &);
#if ENABLE_ANALYZER
@@ -2690,6 +2761,29 @@ c_parser_declaration_or_fndef (c_parser *parser, bool
fndef_ok,
astate);
return result;
}
+
+ specs->attrs = c_parser_handle_disable_coverage (parser, specs->attrs,
+ astate);
+ if (astate.disable_coverage_p)
+ {
+ c_parser_statement_after_labels (parser, NULL, NULL_TREE, NULL,
+ astate);
+ return result;
+ }
+ }
+ else if (specs->typespec_kind == ctsk_none
+ && nested
+ && fallthru_attr_p)
+ {
+ attr_state astate = {};
+ specs->attrs = c_parser_handle_disable_coverage (parser, specs->attrs,
+ astate);
+ if (astate.disable_coverage_p)
+ {
+ c_parser_statement_after_labels (parser, NULL, NULL_TREE, NULL,
+ astate);
+ return result;
+ }
}
/* Provide better error recovery. Note that a type name here is usually
@@ -2788,6 +2882,12 @@ c_parser_declaration_or_fndef (c_parser *parser, bool
fndef_ok,
warning_at (here, OPT_Wattributes,
"%<assume%> attribute not followed by %<;%>");
+
+ attr_state astate = {};
+ specs->attrs = c_parser_handle_disable_coverage (parser, specs->attrs,
+ astate);
+ c_parser_auto_disable_coverage disable_coverage (astate.disable_coverage_p);
+
auto_vec<c_token> omp_declare_simd_attr_clauses;
c_parser_handle_directive_omp_attributes (specs->attrs,
omp_declare_simd_clauses,
@@ -3034,6 +3134,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool
fndef_ok,
finish_decl (d, init_loc, init.value,
init.original_type, asm_name);
result = d;
+ disable_coverage.disable ();
}
}
else
@@ -7596,6 +7697,25 @@ c_parser_handle_musttail (c_parser *parser, tree
std_attrs, attr_state &attr)
return std_attrs;
}
+/* Check if STD_ATTR contains a disable_coverage attribute and remove it.
+ PARSER is the parser and ATTR is the output attr_state. */
+
+static tree
+c_parser_handle_disable_coverage (c_parser *, tree std_attrs, attr_state &attr)
+{
+ if (tree a = lookup_attribute ("gnu", "disable_coverage", std_attrs))
+ {
+ for (; a; a = lookup_attribute ("gnu", "disable_coverage",
+ TREE_CHAIN (a)))
+ if (TREE_VALUE (a))
+ error ("%qs attribute does not take any arguments",
+ "disable_coverage");
+ std_attrs = remove_attribute ("gnu", "disable_coverage", std_attrs);
+ attr.disable_coverage_p = true;
+ }
+ return std_attrs;
+}
+
/* Return a statement before optional series of LABEL_EXPR/CASE_LABEL_EXPRs.
Instead of collecting vectors of labels before each stmt just in case
the statement would be iteration or switch statement for named loops,
@@ -7695,6 +7815,9 @@ c_parser_compound_statement_nostart (c_parser *parser)
{
location_t loc = c_parser_peek_token (parser)->location;
loc = expansion_point_location_if_in_system_header (loc);
+ /* Reset at every step so that attributes don't leak into other
+ statements. */
+ a = {};
bool want_nested_loop = (omp_for_parse_state
? omp_for_parse_state->want_nested_loop
@@ -7778,6 +7901,7 @@ c_parser_compound_statement_nostart (c_parser *parser)
{
std_attrs = c_parser_std_attribute_specifier_sequence (parser);
std_attrs = c_parser_handle_musttail (parser, std_attrs, a);
+ std_attrs = c_parser_handle_disable_coverage (parser, std_attrs, a);
}
if (c_parser_next_token_is_keyword (parser, RID_CASE)
|| c_parser_next_token_is_keyword (parser, RID_DEFAULT)
@@ -7987,6 +8111,7 @@ c_parser_all_labels (c_parser *parser)
{
std_attrs = c_parser_std_attribute_specifier_sequence (parser);
std_attrs = c_parser_handle_musttail (parser, std_attrs, attr);
+ std_attrs = c_parser_handle_disable_coverage (parser, std_attrs, attr);
}
while (c_parser_next_token_is_keyword (parser, RID_CASE)
@@ -8001,6 +8126,8 @@ c_parser_all_labels (c_parser *parser)
{
std_attrs = c_parser_std_attribute_specifier_sequence (parser);
std_attrs = c_parser_handle_musttail (parser, std_attrs, attr);
+ std_attrs = c_parser_handle_disable_coverage (parser, std_attrs,
+ attr);
}
}
if (std_attrs
@@ -8329,6 +8456,15 @@ c_parser_statement_after_labels (c_parser *parser, bool
*if_p,
if (if_p != NULL)
*if_p = false;
+ int coverage_uid = -1;
+ tree_stmt_iterator cov_tsi;
+ if (astate.disable_coverage_p)
+ {
+ coverage_uid = next_disable_coverage_uid ();
+ add_stmt (build_call_disable_coverage (coverage_uid));
+ cov_tsi = tsi_last (cur_stmt_list);
+ }
+
if (c_parser_peek_token (parser)->type != CPP_OPEN_BRACE)
add_debug_begin_stmt (loc);
@@ -8447,7 +8583,8 @@ c_parser_statement_after_labels (c_parser *parser, bool
*if_p,
{
/* Allow '__attribute__((fallthrough));' or
'__attribute__((assume(cond)));' or
- '__attribute__((musttail))) return'. */
+ '__attribute__((musttail))) return' or
+ '__attribute__((disable_coverage)) <stmt>;'. */
tree attrs = c_parser_gnu_attributes (parser);
bool has_assume = lookup_attribute ("assume", attrs);
if (has_assume)
@@ -8476,6 +8613,17 @@ c_parser_statement_after_labels (c_parser *parser, bool
*if_p,
}
goto restart;
}
+ gcc_assert (!astate.disable_coverage_p);
+ attrs = c_parser_handle_disable_coverage (parser, attrs, astate);
+ if (astate.disable_coverage_p)
+ {
+ /* Disable coverage for the next statement, and restore it
+ before this function returns. */
+ coverage_uid = next_disable_coverage_uid ();
+ add_stmt (build_call_disable_coverage (coverage_uid));
+ cov_tsi = tsi_last (cur_stmt_list);
+ goto restart;
+ }
if (attribute_fallthrough_p (attrs))
{
if (c_parser_next_token_is (parser, CPP_SEMICOLON))
@@ -8548,6 +8696,14 @@ c_parser_statement_after_labels (c_parser *parser, bool
*if_p,
protected_set_expr_location (stmt, loc);
parser->in_if_block = in_if_block;
+
+ /* Re-enable coverage once this statement has been completely processed. */
+ if (astate.disable_coverage_p)
+ {
+ for (tsi_next (&cov_tsi); !tsi_end_p (cov_tsi); tsi_next (&cov_tsi))
+ disable_coverage_in_catch_handlers (*cov_tsi, coverage_uid);
+ add_stmt (build_call_enable_coverage (coverage_uid));
+ }
}
/* Parse the condition from an if, do, while or for statements. */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 2dc4863ec52..e7316b07f57 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -13327,6 +13327,62 @@ cp_parser_handle_directive_omp_attributes (cp_parser
*parser, tree *pattrs,
parser->omp_declare_simd->attribs[!start] = pattrs;
}
+/* Check if any coverage is enabled; if not, __attribute__((disable_coverage))
+ is a no-op. */
+bool coverage_instrumentation_p ();
+
+/* Check if STD_ATTR contains a disable_coverage attribute and remove it. If
+ it does, DISABLE_COVERAGE_P will be set to true. If STD_ATTR is
+ error_mark_node, this is a noop. */
+static tree
+cp_parser_handle_disable_coverage (tree std_attrs, bool &disable_coverage_p)
+{
+ if (std_attrs == error_mark_node)
+ return std_attrs;
+
+ if (tree a = lookup_attribute ("gnu", "disable_coverage", std_attrs))
+ {
+ for (; a; a = lookup_attribute ("gnu", "disable_coverage",
+ TREE_CHAIN (a)))
+ if (TREE_VALUE (a))
+ error ("%qs attribute does not take any arguments",
+ "disable_coverage");
+ std_attrs = remove_attribute ("gnu", "disable_coverage", std_attrs);
+ disable_coverage_p = coverage_instrumentation_p ();
+ }
+ return std_attrs;
+}
+
+/* When DISABLE_P is true, automatically insert a pair of .DISABLE/.ENABLE into
+ cur_stmt_list. This automates checking and inserting the calls for all code
+ paths. Used by cp_parser_statement. */
+struct cp_auto_disable_coverage
+{
+ int uid;
+ tree_stmt_iterator tsi;
+ bool disable_coverage_p;
+
+ cp_auto_disable_coverage (bool disable_p) : uid (-1), tsi ({}),
+ disable_coverage_p (disable_p)
+ {
+ if (disable_coverage_p)
+ {
+ uid = next_disable_coverage_uid ();
+ add_stmt (build_call_disable_coverage (uid));
+ tsi = tsi_last (cur_stmt_list);
+ }
+ }
+ ~cp_auto_disable_coverage ()
+ {
+ if (disable_coverage_p)
+ {
+ for (tsi_next (&tsi); !tsi_end_p (tsi); tsi_next (&tsi))
+ disable_coverage_in_catch_handlers (*tsi, uid);
+ add_stmt (build_call_enable_coverage (uid));
+ }
+ }
+};
+
/* Parse a statement.
statement:
@@ -13484,6 +13540,11 @@ cp_parser_statement (cp_parser* parser, tree
in_statement_expr,
}
parser->omp_attrs_forbidden_p = false;
+ bool disable_coverage_p = false;
+ std_attrs = cp_parser_handle_disable_coverage (std_attrs,
+ disable_coverage_p);
+ cp_auto_disable_coverage disable_coverage_handle (disable_coverage_p);
+
/* Remember the location of the first token in the statement. */
cp_token *statement_token = token;
statement_location = token->location;
@@ -17150,6 +17211,17 @@ cp_parser_simple_declaration (cp_parser* parser,
&decl_specifiers.attributes,
&odsd, true);
+ if (decl_specifiers.attributes && !function_definition_allowed_p)
+ {
+ /* Remove the [[gnu::disable_coverage]] attribute so that parsing doesn't
+ fail with "unhandled attribute". It is already handled handled by the
+ statement parsing code, but the declaration re-parses the attribute
+ list. */
+ bool dummy;
+ decl_specifiers.attributes = cp_parser_handle_disable_coverage
+ (decl_specifiers.attributes, dummy);
+ }
+
/* In a block scope, a valid declaration must always have a
decl-specifier-seq. By not trying to parse declarators, we can
resolve the declaration/expression ambiguity more quickly. */
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index a5c687f2667..6cd04330d70 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -9856,6 +9856,84 @@ variable is not live during the call. So:
It is not possible to avoid the warning in this way if the maybe-escaped
variable is a function argument, because those are in scope
for the whole function.
+
+@cindex @code{disable_coverage} statement attribute
+@item disable_coverage
+The @code{disable_coverage} attribute instructs @command{gcov} to ignore
+the statement's contribution towards coverage. This is useful to
+suppress coverage for code that is unreachable yet counts towards
+coverage and pollutes the report, which is a common problem with
+defensive code or when asserting invariants and properties. This
+attribute does nothing unless coverage is enabled, e.g. with
+@option{--coverage} or @option{-fpath-coverage}.
+
+@smallexample
+__attribute__((disable_coverage)) z = x + y;
+[[gnu::disable_coverage]] z = x + y;
+@end smallexample
+
+When @code{disable_coverage} is applied to a compound statement, an if,
+a loop, or any other nested structure, coverage is disabled for the
+whole block.
+
+@smallexample
+ __attribute__((disable_coverage))
+ if (cond)
+ @{
+ /* Coverage is disbled for all statements in this block. */
+ foo ();
+ @}
+ else
+ @{
+ /* Also here. */
+ bar ();
+ @}
+
+ /* Counts towards coverage. */
+ int a = 42;
+
+ __attribute__((disable_coverage))
+ @{
+ /* Does not count towards coverage. */
+ bar (&a);
+ ...
+ @}
+@end smallexample
+
+When using the @code{disable_coverage} attribute after a label, the
+attribute must be preceeded by a null statement, otherwise the attribute
+would be applied to the label itself. This is most common when used in
+a switch statement.
+
+@smallexample
+switch (cond)
+ @{
+ case 0:
+ ; /* Null statement. */
+ __attribute__((disable_coverage))
+ abort_unreachable ();
+ case 1:
+ ...
+ @}
+@end smallexample
+
+Asserts introduce a branch, and the goal of asserting invariants is that
+the assert should not trigger. The asserting branch would be counted as
+the number of branches in the program, but these branches shouldn't or
+can't be taken, yet make full coverage impossible. By disabling
+coverage for asserts we can fix this problem.
+
+@smallexample
+#define REQUIRE(pred, msg) \
+ __attribute__((disable_coverage)) assert (((void)msg, pred))
+
+double div (double x, double y)
+@{
+ /* gcov reports that this function has no branches. */
+ REQUIRE (y != 0, "division by zero");
+ return x / y;
+@}
+@end smallexample
@end table
@node Attribute Syntax
diff --git a/gcc/function.cc b/gcc/function.cc
index 9795dc7c1a0..c490e540a98 100644
--- a/gcc/function.cc
+++ b/gcc/function.cc
@@ -217,6 +217,7 @@ free_after_compilation (struct function *f)
f->cfg = NULL;
f->curr_properties &= ~PROP_cfg;
delete f->cond_uids;
+ delete f->disabled_coverage_blocks;
regno_reg_rtx = NULL;
}
diff --git a/gcc/function.h b/gcc/function.h
index 3190f6e990c..1e593fbb6fb 100644
--- a/gcc/function.h
+++ b/gcc/function.h
@@ -274,6 +274,10 @@ struct GTY(()) function {
the same uid. This is used for condition coverage. */
hash_map <gcond*, unsigned> *GTY((skip)) cond_uids;
+ /* Blocks annotated with [[gnu::disable_coverage]]. These blocks are
+ recorded and instructs gcov to not count them towards coverage. */
+ hash_set <basic_block> *GTY((skip)) disabled_coverage_blocks;
+
/* For function.cc. */
/* Points to the FUNCTION_DECL of this function. */
diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
index 8fe72713b2f..f247a3aa9c2 100644
--- a/gcc/gcov-dump.cc
+++ b/gcc/gcov-dump.cc
@@ -40,6 +40,7 @@ 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_ignore (const char *, unsigned, int, unsigned);
static void tag_counters (const char *, unsigned, int, unsigned);
static void tag_summary (const char *, unsigned, int, unsigned);
extern int main (int, char **);
@@ -80,6 +81,7 @@ static const tag_format_t tag_table[] =
{GCOV_TAG_ARCS, "ARCS", tag_arcs},
{GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
{GCOV_TAG_LINES, "LINES", tag_lines},
+ {GCOV_TAG_IGNORE, "IGNORE", tag_ignore},
{GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
{0, NULL, NULL}
};
@@ -461,6 +463,26 @@ tag_lines (const char *filename ATTRIBUTE_UNUSED,
}
}
+static void
+tag_ignore (const char *filename ATTRIBUTE_UNUSED,
+ unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
+ unsigned depth)
+{
+ if (flag_dump_contents)
+ {
+ unsigned nblocks = GCOV_TAG_IGNORE_NUM (length);
+ printf (" %u blocks ignored", nblocks);
+ for (unsigned i = 0; i != nblocks; ++i)
+ {
+ gcov_position_t position = gcov_position ();
+ unsigned blockno = gcov_read_unsigned ();
+ printf ("\n");
+ print_prefix (filename, depth, position);
+ printf (VALUE_PADDING_PREFIX "block %u", blockno);
+ }
+ }
+}
+
static void
tag_counters (const char *filename ATTRIBUTE_UNUSED,
unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 5a0b109c49b..74fcc1d2165 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -268,6 +268,9 @@ typedef uint64_t gcov_type_unsigned;
#define GCOV_TAG_PATHS_LENGTH(NUM) ((NUM) * GCOV_WORD_SIZE)
#define GCOV_TAG_PATHS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE))
#define GCOV_TAG_LINES ((gcov_unsigned_t)0x01450000)
+#define GCOV_TAG_IGNORE ((gcov_unsigned_t)0x01510000)
+#define GCOV_TAG_IGNORE_LENGTH(NUM) ((NUM) * GCOV_WORD_SIZE)
+#define GCOV_TAG_IGNORE_NUM(LENGTH) ((LENGTH / GCOV_WORD_SIZE))
#define GCOV_TAG_COUNTER_BASE ((gcov_unsigned_t)0x01a10000)
#define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
#define GCOV_TAG_COUNTER_NUM(LENGTH) ((LENGTH / GCOV_WORD_SIZE) / 2)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 5bed44841c0..661f13e4ace 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -126,6 +126,9 @@ struct arc_info
/* Is a false arc. */
unsigned int false_value : 1;
+ /* Is ignored arc by [[gnu::disable_coverage]]. */
+ unsigned int ignored : 1;
+
/* Links to next arc on src and dst lists. */
struct arc_info *succ_next;
struct arc_info *pred_next;
@@ -146,15 +149,43 @@ public:
path is covered. */
vector<gcov_type_unsigned> covered;
+ /* The prime paths after [[gnu::disable_coverage]] has been taken into
+ account. This is empty unless [[gnu::disable_coverage]] is used,
+ otherwise it should be smaller than PATHS. The paths are
+ lexicographically sorted. */
+ vector<vector<unsigned>> xpaths;
+
+ /* The covered paths after [[gnu::disable_coverage]] has been taken into
+ account. Like with COVERED, the bit N is set if the Nth path in XPATHS is
+ covered. */
+ vector<gcov_type_unsigned> xcovered;
+
/* The size (in bits) of each bucket. */
static const size_t
bucketsize = sizeof (gcov_type_unsigned) * BITS_PER_UNIT;
- /* Count the covered paths. */
+ /* Helper for getting the right path set. */
+ const vector<vector<unsigned>>& get_paths () const
+ { return !ignored_p () ? paths : xpaths; }
+
+ /* Get the number of paths, accounting for ignored blocks. */
+ size_t path_count () const
+ { return get_paths ().size (); }
+
+ /* Get the number of ignored paths. This is 0 unless
+ [[gnu::disable_coverage]] is used. */
+ size_t ignored_count () const
+ { return paths.size () - path_count (); }
+
+ /* Check if there are any paths ignored by [[gnu::disable_coverage]]. */
+ bool ignored_p () const
+ { return !xpaths.empty (); }
+
+ /* Count the covered paths, accounting for [[gnu::disable_coverage]]. */
unsigned covered_paths () const
{
unsigned cnt = 0;
- for (gcov_type_unsigned v : covered)
+ for (gcov_type_unsigned v : (!ignored_p () ? covered : xcovered))
cnt += popcount_hwi (v);
return cnt;
}
@@ -164,10 +195,14 @@ public:
{
if (covered.empty ())
return false;
+
+ const auto& cov = !ignored_p () ? covered : xcovered;
const size_t bucket = n / bucketsize;
const uint64_t bit = n % bucketsize;
- return covered[bucket] & (gcov_type_unsigned (1) << bit);
+ return cov[bucket] & (gcov_type_unsigned (1) << bit);
}
+
+ void ignore_blocks (const vector<bool>& ignored);
};
/* Describes which locations (lines and files) are associated with
@@ -244,6 +279,9 @@ public:
/* Block is a landing pad for longjmp or throw. */
unsigned is_nonlocal_return : 1;
+ /* Block is ignored by [[gnu::disable_coverage]] */
+ unsigned ignored : 1;
+
condition_info conditions;
vector<block_location_info> locations;
@@ -266,7 +304,7 @@ public:
block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred
(0),
id (0), count (0), count_valid (0), valid_chain (0), invalid_chain (0),
exceptional (0), is_call_site (0), is_call_return (0), is_nonlocal_return
(0),
- locations (), chain (NULL)
+ ignored (0), locations (), chain (NULL)
{
cycle.arc = NULL;
}
@@ -295,10 +333,12 @@ public:
unsigned exists : 1;
unsigned unexceptional : 1;
unsigned has_unexecuted_block : 1;
+ /* Ignored by [[gnu::disable_coverage]]. */
+ unsigned ignored : 1;
};
line_info::line_info (): count (0), branches (), blocks (), exists (false),
- unexceptional (0), has_unexecuted_block (0)
+ unexceptional (0), has_unexecuted_block (0), ignored (0)
{
}
@@ -385,6 +425,10 @@ public:
/* Next function. */
class function_info *next;
+ /* Blocks ignored with [[gnu::disable_coverage]]. If any block is ignored
+ this is non-empty, and the Nth bit is true if N is ignored. */
+ vector<bool> ignored_blocks;
+
/* Get demangled name of a function. The demangled name
is converted when it is used for the first time. */
char *get_demangled_name ()
@@ -432,21 +476,26 @@ struct coverage_info
{
int lines;
int lines_executed;
+ int lines_ignored;
int branches;
int branches_executed;
int branches_taken;
+ int branches_ignored;
int conditions;
int conditions_covered;
+ int conditions_ignored;
int calls;
int calls_executed;
+ int calls_ignored;
char *name;
unsigned paths;
unsigned paths_covered;
+ unsigned paths_ignored;
};
/* Describes a file mentioned in the block graph. Contains an array
@@ -608,6 +657,7 @@ static unsigned object_runs;
static unsigned total_lines;
static unsigned total_executed;
+static unsigned total_ignored;
/* Modification time of graph file. */
@@ -814,7 +864,7 @@ static void add_branch_counts (coverage_info *, const
arc_info *);
static void add_condition_counts (coverage_info *, const block_info *);
static void add_path_counts (coverage_info &, const function_info &);
static void add_line_counts (coverage_info *, function_info *);
-static void executed_summary (unsigned, unsigned);
+static void executed_summary (unsigned, unsigned, unsigned);
static void function_summary (const coverage_info *);
static void file_summary (const coverage_info *);
static const char *format_gcov (gcov_type, gcov_type, int);
@@ -861,15 +911,221 @@ bool function_info::group_line_p (unsigned n, unsigned
src_idx)
return is_group && src == src_idx && start_line <= n && n <= end_line;
}
-/* Find the arc that connects BLOCK to the block with id DEST. This
- edge must exist. */
-static const arc_info&
+/* Check if the block ID is a tombstone. */
+static bool
+tombstone_p (unsigned id)
+{
+ return id == unsigned (-1);
+};
+
+/* Remove tombstones from VEC. Preserves the order of remaining values. */
+static vector<unsigned>
+remove_tombstones (vector<unsigned> vec)
+{
+ vec.erase (remove_if (vec.begin (), vec.end (), tombstone_p), vec.end ());
+ return vec;
+}
+
+/* Check if SUB is a a proper contiguous subsequence of SUPER with tombstones
+ functioning as wildcards.
+
+ If SUB and SUPER would be equal if tombstones are removed, SUB is not a
+ proper subsequence and this function returns false.
+
+ Examples:
+
+ SUB: 2 -1 12
+ SUPER: 2 -1 -1 12
+ Returns false because both sequences become [2 12] without tombstones.
+
+ SUB: 2 -1 12
+ SUPER: 2 7 12
+ Returns true because 2 12 appear in that order in SUPER and there is a
+ tombstone between 2 and 12.
+
+ SUB: 2 12
+ SUPER: 2 7 12
+ Returns false because 2 and 12 are not consecutive in SUPER.
+
+ SUB: 12 7
+ SUPER: 2 7 12
+ Returns false because 7 is before 12 in SUPER.
+
+ SUB: -1 7 12
+ SUPER: -1 7 -1 12 -1
+ Returns false because both sequences are 7 12 once tombstones are removed.
+*/
+static bool
+tombstone_subsequence_p (const vector<unsigned>& sub,
+ const vector<unsigned>& super)
+{
+ if (&sub == &super)
+ return false;
+
+ auto xend = sub.end ();
+ auto yend = super.end ();
+ auto xitr = find_if_not (sub.begin (), xend, tombstone_p);
+ auto yitr = find_if_not (super.begin (), yend, tombstone_p);
+
+ /* If SUB is empty or all tombstones it is included in any other path. */
+ if (xitr == xend)
+ return true;
+ /* If SUPER is empty or all tombstones it does not include anything. */
+ if (yitr == yend)
+ return false;
+
+ bool equivalent = *yitr == *xitr;
+ /* Find the position in SUPER where the SUB may start. */
+ if (!equivalent)
+ {
+ yitr = find (yitr, yend, *xitr);
+ if (yitr == yend)
+ return false;
+ }
+
+ for (; xitr != xend; ++xitr, ++yitr)
+ if (tombstone_p (*xitr))
+ {
+ /* Skip past any tombstones to find the next value. We need to compare
+ to the next non-tombstone value in SUPER to know if we skipped any
+ values to check for equivalence, otherwise this could just be
+ std::find for SUPER. */
+ xitr = find_if_not (xitr, xend, tombstone_p);
+ yitr = find_if_not (yitr, yend, tombstone_p);
+
+ /* If there are no more non-tombstone blocks i SUB we're almost done,
+ but we still need to if there are more blocks in SUPER. */
+ if (xitr == xend)
+ return yitr != yend || !equivalent;
+
+ if (yitr == yend)
+ return false;
+
+ /* Now check for equivalence and look for the value in SUPER. This is
+ a no-op if we found it already. */
+ equivalent = equivalent && *yitr == *xitr;
+ yitr = find (yitr, yend, *xitr);
+ if (yitr == yend)
+ return false;
+ }
+ else if (*yitr != *xitr)
+ return false;
+
+ yitr = find_if_not (yitr, yend, tombstone_p);
+ return yitr == yend && !equivalent;
+}
+
+/* Check if NEEDLE is a proper subsequence of any sequence in HAYSTACK except
+ itself. Ignored blocks/tombstones function as wildcards and match any
+ subsequence. If two sequences are equal once tombstones are removed they
+ are not proper subsequences of eachother.
+
+ We may get odd sequence when we remove parts of a path, so we
+ extend the when the path A subsumes B to include non-contiguous
+ subsequences.
+
+ Given a set of prime paths:
+ 2 3 4 12
+ 2 3 5 6 12
+ 2 3 5 7 8 10 12
+ 2 3 5 7 8 9 10 12
+ 2 3 5 7 8 9 11 12
+
+ We have a blacklist of 3 4 5 6 8 9 10 which means these nodes should be
+ removed from all paths. If we replace blacklisted nodes with tombstones
+ (-1) and remove duplicates we get:
+ 2 -1 12
+ 2 -1 7 -1 11 12
+ 2 -1 7 -1 12
+
+ A path is prime if it is not a subpath of any other paths. Ignored segments
+ may be covered by any sequence of nodes, so the path:
+ 2 -1 7 -1 11 12
+ would subsume (<:) the other paths:
+ 2 -1 12 <: 2 [7 11] 12
+ 2 -1 7 12 <: 2 7 [11] 12
+
+ Thus the only prime path is 2 7 11 12. */
+static bool
+subsumed_by_any_p (const vector<unsigned>& needle,
+ const vector<vector<unsigned>>& haystack)
+{
+ if (all_of (needle.begin (), needle.end (), tombstone_p))
+ return true;
+ for (const auto& seq : haystack)
+ if (tombstone_subsequence_p (needle, seq))
+ return true;
+ return false;
+}
+
+/* Compute the new paths and coverage by ignoring the blocks in IGNORED. Does
+ nothing when IGNORED is empty. This only adds the new interpretation and
+ does not change the observed path and coverage info. */
+void
+path_info::ignore_blocks (const vector<bool>& ignored)
+{
+ if (ignored.empty ())
+ return;
+
+ const unsigned tombstone = unsigned (-1);
+ /* Clean up the paths by replacing ignored blocks with tombstones. */
+ vector<vector<unsigned>> ipaths;
+ ipaths.reserve (paths.size ());
+ for (const auto& path : paths)
+ {
+ vector<unsigned> tmp;
+ tmp.reserve (path.size ());
+ for (auto v : path)
+ tmp.push_back (!ignored[v] ? v : tombstone);
+ ipaths.push_back (std::move (tmp));
+ }
+
+ /* Changing paths means some paths may turn into subpaths, so we find and
+ store the new prime paths mapped to the original indices for later.
+ The new paths are map both sorts the paths and filters duplicates
+ duplicates. */
+ map<vector<unsigned> /* path */, vector<size_t> /* indices */> nextpaths;
+ for (size_t i = 0; i != ipaths.size (); ++i)
+ if (!subsumed_by_any_p (ipaths[i], ipaths))
+ nextpaths[remove_tombstones (ipaths[i])].push_back (i);
+
+ /* Record the coverage of the new paths. The new paths may be the result
+ of merging paths, and if either original path is covered then the merged
+ path should be covered. */
+ vector<gcov_type_unsigned> nextcovered;
+ const size_t nbits = path_info::bucketsize;
+ const size_t nbuckets = (nextpaths.size () + (nbits - 1)) / nbits;
+ nextcovered.resize (nbuckets);
+ std::size_t n = 0;
+ for (const auto& np : nextpaths)
+ {
+ const size_t bucket = n / bucketsize;
+ const uint64_t bit = n % bucketsize;
+ for (size_t index : np.second)
+ if (covered_p (index))
+ {
+ nextcovered[bucket] |= (gcov_type_unsigned (1) << bit);
+ break;
+ }
+ n++;
+ }
+ xcovered.swap (nextcovered);
+
+ /* Store the new paths. The map iteration outputs the paths
+ lexicographically ordered. */
+ for (auto& p : nextpaths)
+ xpaths.push_back (std::move (p.first));
+}
+
+/* Find the arc that connects BLOCK to the block with id DEST, or nullptr if it
+ doesn't exist. */
+static const arc_info*
find_arc (const block_info &block, unsigned dest)
{
for (const arc_info *arc = block.succ; arc; arc = arc->succ_next)
if (arc->dst->id == dest)
- return *arc;
- gcc_assert (false);
+ return arc;
+ return nullptr;
}
/* Cycle detection!
@@ -1079,7 +1335,7 @@ main (int argc, char **argv)
}
if (!flag_use_stdout)
- executed_summary (total_lines, total_executed);
+ executed_summary (total_lines, total_executed, total_ignored);
return return_code;
}
@@ -1384,7 +1640,11 @@ output_intermediate_json_line (json::array *object,
for (it = line->branches.begin (); it != line->branches.end ();
it++)
{
- if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
+ if ((*it)->ignored)
+ {
+ /* Skip. */
+ }
+ else if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
{
json::object *branch = new json::object ();
branch->set_integer ("count", (*it)->count);
@@ -1412,6 +1672,9 @@ output_intermediate_json_line (json::array *object,
vector<block_info *>::const_iterator it;
for (it = line->blocks.begin (); it != line->blocks.end (); it++)
{
+ if ((*it)->ignored)
+ continue;
+
const condition_info& info = (*it)->conditions;
if (info.n_terms == 0)
continue;
@@ -1518,12 +1781,13 @@ static void
json_set_prime_path_coverage (json::object &function, function_info &info)
{
json::array *jpaths = new json::array ();
- function.set_integer ("total_prime_paths", info.paths.paths.size ());
+ function.set_integer ("total_prime_paths", info.paths.path_count ());
function.set_integer ("covered_prime_paths", info.paths.covered_paths ());
+ function.set_integer ("ignored_prime_paths", info.paths.ignored_count ());
function.set ("prime_path_coverage", jpaths);
size_t pathno = 0;
- for (const vector<unsigned> &path : info.paths.paths)
+ for (const vector<unsigned> &path : info.paths.get_paths ())
{
if (info.paths.covered_p (pathno++))
continue;
@@ -1544,14 +1808,16 @@ json_set_prime_path_coverage (json::object &function,
function_info &info)
const char *edge_kind = "";
if (i + 1 != path.size ())
{
- const arc_info &arc = find_arc (block, path[i+1]);
- if (arc.false_value)
+ const arc_info *arc = find_arc (block, path[i+1]);
+ if (!arc)
+ edge_kind = "(ignore)";
+ else if (arc->false_value)
edge_kind = "true";
- else if (arc.false_value)
+ else if (arc->false_value)
edge_kind = "false";
- else if (arc.fall_through)
+ else if (arc->fall_through)
edge_kind = "fallthru";
- else if (arc.is_throw)
+ else if (arc->is_throw)
edge_kind = "throw";
}
@@ -1607,6 +1873,9 @@ output_json_intermediate_file (json::array *json_files,
source_info *src)
function->set_integer ("end_column", (*it)->end_column);
function->set_integer ("blocks", (*it)->get_block_count ());
function->set_integer ("blocks_executed", (*it)->blocks_executed);
+ function->set_integer ("blocks_ignored",
+ count ((*it)->ignored_blocks.begin (),
+ (*it)->ignored_blocks.end (), true));
function->set_integer ("execution_count", (*it)->blocks[0].count);
json_set_prime_path_coverage (*function, **it);
@@ -1770,6 +2039,19 @@ process_all_functions (void)
function_info *fn = *it;
unsigned src = fn->src;
+ if (!fn->ignored_blocks.empty ())
+ {
+ /* Set the ignore flag on blocks, arcs. */
+ for (block_info &b : fn->blocks)
+ if (fn->ignored_blocks[b.id])
+ {
+ b.ignored = 1;
+ for (arc_info *arc = b.succ; arc; arc = arc->succ_next)
+ arc->ignored = 1;
+ for (arc_info *arc = b.pred; arc; arc = arc->pred_next)
+ arc->ignored = 1;
+ }
+ }
if (!fn->counts.empty () || no_data_file)
{
source_info *s = &sources[src];
@@ -1807,6 +2089,10 @@ process_all_functions (void)
}
}
}
+
+ if (block->ignored)
+ for (unsigned ln : block->locations[i].lines)
+ s->lines[ln].ignored = 1;
}
}
@@ -1827,6 +2113,7 @@ process_all_functions (void)
/* For path coverage. */
find_prime_paths (fn);
+ fn->paths.ignore_blocks (fn->ignored_blocks);
}
else
{
@@ -1953,6 +2240,7 @@ generate_results (const char *file_name)
file_summary (&src->coverage);
total_lines += src->coverage.lines;
total_executed += src->coverage.lines_executed;
+ total_ignored += src->coverage.lines_ignored;
if (flag_gcov_file)
{
if (flag_json_format)
@@ -2337,6 +2625,7 @@ read_graph_file (void)
arc->fall_through = !!(flags & GCOV_ARC_FALLTHROUGH);
arc->true_value = !!(flags & GCOV_ARC_TRUE);
arc->false_value = !!(flags & GCOV_ARC_FALSE);
+ arc->ignored = 0;
arc->succ_next = src_blk->succ;
src_blk->succ = arc;
@@ -2383,6 +2672,21 @@ read_graph_file (void)
}
}
}
+ else if (fn && tag == GCOV_TAG_IGNORE)
+ {
+ const unsigned nblocks = GCOV_TAG_IGNORE_NUM (length);
+ if (!fn->ignored_blocks.empty ())
+ fnotice (stderr, "%s:already seen ignored blocks for '%s'\n",
+ bbg_file_name, fn->get_name ());
+ fn->ignored_blocks.resize (fn->blocks.size (), false);
+ for (unsigned i = 0; i != nblocks; ++i)
+ {
+ const unsigned idx = gcov_read_unsigned ();
+ if (idx >= fn->blocks.size ())
+ goto corrupt;
+ fn->ignored_blocks[idx] = true;
+ }
+ }
else if (fn && tag == GCOV_TAG_CONDS)
{
unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
@@ -2960,16 +3264,20 @@ add_branch_counts (coverage_info *coverage, const
arc_info *arc)
if (arc->is_call_non_return)
{
coverage->calls++;
- if (arc->src->count)
+ if (arc->ignored)
+ coverage->calls_ignored++;
+ else if (arc->src->count)
coverage->calls_executed++;
}
else if (!arc->is_unconditional)
{
coverage->branches++;
- if (arc->src->count)
+ if (arc->src->count && !arc->ignored)
coverage->branches_executed++;
- if (arc->count)
+ if (arc->count && !arc->ignored)
coverage->branches_taken++;
+ if (arc->ignored)
+ coverage->branches_ignored++;
}
}
@@ -2979,7 +3287,10 @@ 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 ();
+ if (block->ignored)
+ coverage->conditions_ignored += 2 * block->conditions.n_terms;
+ else
+ coverage->conditions_covered += block->conditions.popcount ();
}
/* Increment path totals, number of paths and number of covered paths,
@@ -2988,8 +3299,9 @@ add_condition_counts (coverage_info *coverage, const
block_info *block)
static void
add_path_counts (coverage_info &coverage, const function_info &fn)
{
- coverage.paths += fn.paths.paths.size ();
+ coverage.paths += fn.paths.path_count ();
coverage.paths_covered += fn.paths.covered_paths ();
+ coverage.paths_ignored += fn.paths.ignored_count ();
}
/* Format COUNT, if flag_human_readable_numbers is set, return it human
@@ -3048,11 +3360,15 @@ format_gcov (gcov_type top, gcov_type bottom, int
decimal_places)
/* Summary of execution */
static void
-executed_summary (unsigned lines, unsigned executed)
+executed_summary (unsigned lines, unsigned executed, unsigned ignored)
{
- if (lines)
+ if (lines && ignored == 0)
fnotice (stdout, "Lines executed:%s of %d\n",
format_gcov (executed, lines, 2), lines);
+ else if (lines && ignored > 0)
+ fnotice (stdout, "Lines executed:%s of %d (%d of %d ignored)\n",
+ format_gcov (executed, lines - ignored, 2), lines - ignored,
+ ignored, lines);
else
fnotice (stdout, "No executable lines\n");
}
@@ -3063,44 +3379,72 @@ static void
function_summary (const coverage_info *coverage)
{
fnotice (stdout, "%s '%s'\n", "Function", coverage->name);
- executed_summary (coverage->lines, coverage->lines_executed);
+ executed_summary (coverage->lines, coverage->lines_executed,
+ coverage->lines_ignored);
if (coverage->branches)
{
- fnotice (stdout, "Branches executed:%s of %d\n",
- format_gcov (coverage->branches_executed, coverage->branches, 2),
- coverage->branches);
+ const int branches = coverage->branches - coverage->branches_ignored;
+ if (coverage->branches_ignored == 0)
+ fnotice (stdout, "Branches executed:%s of %d\n",
+ format_gcov (coverage->branches_executed, coverage->branches,
+ 2),
+ coverage->branches);
+ else
+ fnotice (stdout, "Branches executed:%s of %d (%d of %d ignored)\n",
+ format_gcov (coverage->branches_executed, branches, 2),
+ branches, coverage->branches_ignored, coverage->branches);
fnotice (stdout, "Taken at least once:%s of %d\n",
- format_gcov (coverage->branches_taken, coverage->branches, 2),
- coverage->branches);
+ format_gcov (coverage->branches_taken, branches, 2),
+ branches);
}
else
fnotice (stdout, "No branches\n");
- if (coverage->calls)
+ if (coverage->calls && coverage->calls == 0)
fnotice (stdout, "Calls executed:%s of %d\n",
format_gcov (coverage->calls_executed, coverage->calls, 2),
coverage->calls);
+ else if (coverage->calls && coverage->calls_ignored > 0)
+ fnotice (stdout, "Calls executed:%s of %d (%d of %d ignored)\n",
+ format_gcov (coverage->calls_executed, coverage->calls
+ - coverage->calls_ignored, 2),
+ coverage->calls - coverage->calls_ignored,
+ coverage->calls_ignored, coverage->calls);
else
fnotice (stdout, "No calls\n");
if (flag_conditions)
{
- if (coverage->conditions)
+ if (coverage->conditions && coverage->conditions_ignored == 0)
fnotice (stdout, "Condition outcomes covered:%s of %d\n",
format_gcov (coverage->conditions_covered,
coverage->conditions, 2),
coverage->conditions);
+ if (coverage->conditions && coverage->conditions_ignored > 0)
+ fnotice (stdout, "Condition outcomes covered:%s of %d"
+ " (%d of %d ignored)\n",
+ format_gcov (coverage->conditions_covered,
+ coverage->conditions
+ - coverage->conditions_ignored, 2),
+ coverage->conditions - coverage->conditions_ignored,
+ coverage->conditions_ignored, coverage->conditions);
else
fnotice (stdout, "No conditions\n");
}
if (flag_prime_paths)
{
- if (coverage->paths)
+ if (coverage->paths && coverage->paths_ignored == 0)
fnotice (stdout, "Prime paths covered:%s of %d\n",
format_gcov (coverage->paths_covered, coverage->paths, 2),
coverage->paths);
+ else if (coverage->paths && coverage->paths_ignored > 0)
+ fnotice (stdout, "Prime paths covered:%s of %d (%u of %u ignored)\n",
+ format_gcov (coverage->paths_covered, coverage->paths
+ - coverage->paths_ignored, 2),
+ coverage->paths - coverage->paths_ignored,
+ coverage->paths_ignored, coverage->paths);
else
fnotice (stdout, "No path information\n");
}
@@ -3112,7 +3456,8 @@ static void
file_summary (const coverage_info *coverage)
{
fnotice (stdout, "%s '%s'\n", "File", coverage->name);
- executed_summary (coverage->lines, coverage->lines_executed);
+ executed_summary (coverage->lines, coverage->lines_executed,
+ coverage->lines_ignored);
if (flag_branches)
{
@@ -3322,7 +3667,9 @@ add_line_counts (coverage_info *coverage, function_info
*fn)
{
if (!line->exists)
coverage->lines++;
- if (!line->count && block->count)
+ if (line->ignored)
+ coverage->lines_ignored++;
+ if (!line->count && block->count && !line->ignored)
coverage->lines_executed++;
}
line->exists = 1;
@@ -3342,7 +3689,9 @@ add_line_counts (coverage_info *coverage, function_info
*fn)
{
if (!line->exists)
coverage->lines++;
- if (!line->count && block->count)
+ if (!line->exists && line->ignored)
+ coverage->lines_ignored++;
+ if (!line->count && block->count && !line->ignored)
coverage->lines_executed++;
}
line->exists = 1;
@@ -3429,7 +3778,9 @@ static void accumulate_line_info (line_info *line,
source_info *src,
if (line->exists && add_coverage)
{
src->coverage.lines++;
- if (line->count)
+ if (line->ignored)
+ src->coverage.lines_ignored++;
+ if (line->count && !line->ignored)
src->coverage.lines_executed++;
}
}
@@ -3482,7 +3833,9 @@ accumulate_line_counts (source_info *src)
if (!src_line->exists)
src->coverage.lines++;
- if (!src_line->count && fn_line->count)
+ if (!src_line->exists && src_line->ignored)
+ src->coverage.lines_ignored++;
+ if (!src_line->count && fn_line->count && !src_line->ignored)
src->coverage.lines_executed++;
src_line->count += fn_line->count;
@@ -3508,6 +3861,8 @@ output_conditions (FILE *gcov_file, const block_info
*binfo)
const condition_info& info = binfo->conditions;
if (info.n_terms == 0)
return;
+ if (binfo->ignored)
+ return;
const int expected = 2 * info.n_terms;
const int got = info.popcount ();
@@ -3535,7 +3890,9 @@ output_conditions (FILE *gcov_file, const block_info
*binfo)
static int
output_branch_count (FILE *gcov_file, int ix, const arc_info *arc)
{
- if (arc->is_call_non_return)
+ if (arc->ignored)
+ return 0;
+ else if (arc->is_call_non_return)
{
if (arc->src->count)
{
@@ -3610,13 +3967,14 @@ print_prime_path_lines (FILE *gcov_file, const
function_info &fn,
const char *edge_kind = "";
if (k + 1 != path.size ())
{
- gcc_checking_assert (block.id == path[k]);
- const arc_info &arc = find_arc (block, path[k+1]);
- if (arc.true_value)
+ const arc_info *arc = find_arc (block, path[k+1]);
+ if (!arc)
+ edge_kind = "(ignore)";
+ else if (arc->true_value)
edge_kind = "(true)";
- else if (arc.false_value)
+ else if (arc->false_value)
edge_kind = "(false)";
- else if (arc.is_throw)
+ else if (arc->is_throw)
edge_kind = "(throw)";
}
@@ -3680,12 +4038,14 @@ print_prime_path_source (FILE *gcov_file, const
function_info &fn,
const char *edge_kind = "";
if (k + 1 != path.size ())
{
- const arc_info &arc = find_arc (block, path[k+1]);
- if (arc.true_value)
+ const arc_info *arc = find_arc (block, path[k+1]);
+ if (!arc)
+ edge_kind = "(ignore)";
+ else if (arc->true_value)
edge_kind = "(true)";
- else if (arc.false_value)
+ else if (arc->false_value)
edge_kind = "(false)";
- else if (arc.is_throw)
+ else if (arc->is_throw)
edge_kind = "(throw)";
}
@@ -3725,8 +4085,16 @@ output_path_coverage (FILE *gcov_file, const
function_info *fn)
if (!flag_prime_paths)
return 0;
- if (fn->paths.paths.empty ())
+ const path_info& paths = fn->paths;
+ if (fn->paths.get_paths ().empty ())
fnotice (gcov_file, "path coverage omitted\n");
+ else if (paths.ignored_p ())
+ fnotice (gcov_file, "Prime paths covered %u of " HOST_SIZE_T_PRINT_UNSIGNED
+ " (" HOST_SIZE_T_PRINT_UNSIGNED " of " HOST_SIZE_T_PRINT_UNSIGNED
+ " ignored)\n", fn->paths.covered_paths (),
+ (fmt_size_t)fn->paths.path_count (),
+ (fmt_size_t)fn->paths.ignored_count (),
+ (fmt_size_t)fn->paths.paths.size ());
else
fnotice (gcov_file, "paths covered %u of " HOST_SIZE_T_PRINT_UNSIGNED "\n",
fn->paths.covered_paths (), (fmt_size_t)fn->paths.paths.size ());
@@ -3734,14 +4102,14 @@ output_path_coverage (FILE *gcov_file, const
function_info *fn)
if (flag_prime_paths_lines_uncovered || flag_prime_paths_lines_covered)
{
unsigned pathno = 0;
- for (const vector<unsigned> &path : fn->paths.paths)
+ for (const vector<unsigned> &path : fn->paths.get_paths ())
print_prime_path_lines (gcov_file, *fn, path, pathno++);
}
if (flag_prime_paths_source_uncovered || flag_prime_paths_source_covered)
{
unsigned pathno = 0;
- for (const vector<unsigned> &path : fn->paths.paths)
+ for (const vector<unsigned> &path : fn->paths.get_paths ())
print_prime_path_source (gcov_file, *fn, path, pathno++);
}
return 1;
@@ -3836,13 +4204,19 @@ pad_count_string (string &s)
static void
output_line_beginning (FILE *f, bool exists, bool unexceptional,
bool has_unexecuted_block,
+ bool ignored,
gcov_type count, unsigned line_num,
const char *exceptional_string,
const char *unexceptional_string,
unsigned int maximum_count)
{
string s;
- if (exists)
+ if (ignored)
+ {
+ s = "#";
+ pad_count_string (s);
+ }
+ else if (exists)
{
if (count > 0)
{
@@ -3935,6 +4309,7 @@ output_line_details (FILE *f, const line_info *line,
unsigned line_num)
{
output_line_beginning (f, line->exists,
(*it)->exceptional, false,
+ (*it)->ignored,
(*it)->count, line_num,
"%%%%%", "$$$$$", 0);
fprintf (f, "-block %d", (*it)->id);
@@ -4092,14 +4467,15 @@ output_lines (FILE *gcov_file, const source_info *src)
/* For lines which don't exist in the .bb file, print '-' before
the source line. For lines which exist but were never
- executed, print '#####' or '=====' before the source line.
- Otherwise, print the execution count before the source line.
- There are 16 spaces of indentation added before the source
- line so that tabs won't be messed up. */
+ executed, print '#####' or '=====' before the source line. For lines
+ that were ignored, print '#'. Otherwise, print the execution count
+ before the source line. There are 16 spaces of indentation added
+ before the source line so that tabs won't be messed up. */
if (line_num <= filtered_line_end)
{
output_line_beginning (gcov_file, line->exists, line->unexceptional,
- line->has_unexecuted_block, line->count,
+ line->has_unexecuted_block, line->ignored,
+ line->count,
line_num, "=====", "#####",
src->maximum_count);
@@ -4138,13 +4514,15 @@ output_lines (FILE *gcov_file, const source_info *src)
/* For lines which don't exist in the .bb file, print '-'
before the source line. For lines which exist but
were never executed, print '#####' or '=====' before
- the source line. Otherwise, print the execution count
- before the source line.
+ the source line. For lines that were ignored, print '#'.
+ Otherwise, print the execution count before the source
+ line.
There are 16 spaces of indentation added before the source
line so that tabs won't be messed up. */
output_line_beginning (gcov_file, line->exists,
line->unexceptional,
line->has_unexecuted_block,
+ line->ignored,
line->count,
l, "=====", "#####",
src->maximum_count);
diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
index d879568c6e3..c73e033e8de 100644
--- a/gcc/internal-fn.cc
+++ b/gcc/internal-fn.cc
@@ -958,6 +958,22 @@ expand_FALLTHROUGH (internal_fn, gcall *call)
"invalid use of attribute %<fallthrough%>");
}
+/* This should get pruned in the profiling pass. */
+
+static void
+expand_DISABLE_COVERAGE (internal_fn, gcall *)
+{
+ gcc_unreachable ();
+}
+
+/* This should get pruned in the profiling pass. */
+
+static void
+expand_ENABLE_COVERAGE (internal_fn, gcall *)
+{
+ gcc_unreachable ();
+}
+
/* Return minimum precision needed to represent all values
of ARG in SIGNed integral type. */
diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def
index 084a9271631..a64782f109b 100644
--- a/gcc/internal-fn.def
+++ b/gcc/internal-fn.def
@@ -584,6 +584,10 @@ DEF_INTERNAL_FN (ATOMIC_XOR_FETCH_CMP_0, ECF_LEAF, NULL)
[[fallthrough]] at the end of C++ loop body. */
DEF_INTERNAL_FN (FALLTHROUGH, ECF_LEAF | ECF_NOTHROW, NULL)
+/* To implement [[gnu::disable_coverage]]. */
+DEF_INTERNAL_FN (DISABLE_COVERAGE, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
+DEF_INTERNAL_FN (ENABLE_COVERAGE, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
+
/* To implement __builtin_launder. */
DEF_INTERNAL_FN (LAUNDER, ECF_LEAF | ECF_NOTHROW | ECF_NOVOPS, NULL)
diff --git a/gcc/profile.cc b/gcc/profile.cc
index 21e33b985dc..bb75ec8555f 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -1205,6 +1205,30 @@ read_thunk_profile (struct cgraph_node *node)
return;
}
+/* Disable coverage for BB. This is used for [[gnu::disable_coverage]]. */
+void
+disable_coverage (basic_block bb)
+{
+ if (!cfun->disabled_coverage_blocks)
+ cfun->disabled_coverage_blocks = new hash_set<basic_block> ();
+ cfun->disabled_coverage_blocks->add (bb);
+}
+
+/* Check if BB has coverage disabled by [[gnu::disable_coverage]]. */
+bool
+coverage_disabled_p (basic_block bb)
+{
+ return cfun->disabled_coverage_blocks
+ && cfun->disabled_coverage_blocks->contains (bb);
+}
+
+/* Check if any blocks are disabled by [[gnu::disable_coverage]] in the current
+ function. */
+static bool
+any_block_disabled_p ()
+{
+ return cfun->disabled_coverage_blocks;
+}
/* Instrument and/or analyze program behavior based on program the CFG.
@@ -1538,6 +1562,19 @@ branch_prob (bool thunk)
gcov_write_length (offset);
}
+ /* Disabled blocks. Lines, arcs, path segments through ignored blocks
+ should not count towards coverage. Ignoring coverage is a amtter of
+ interpretation and does not change the instrumentation; gcov sorts it
+ out. */
+ if (any_block_disabled_p ())
+ {
+ offset = gcov_write_tag (GCOV_TAG_IGNORE);
+ FOR_EACH_BB_FN (bb, cfun)
+ if (coverage_disabled_p (bb))
+ gcov_write_unsigned (bb->index);
+ gcov_write_length (offset);
+ }
+
/* Line numbers. */
/* Initialize the output. */
output_location (&streamed_locations, NULL, 0, NULL, NULL);
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-24.C
b/gcc/testsuite/g++.dg/gcov/gcov-24.C
new file mode 100644
index 00000000000..f3a2d76ee3b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-24.C
@@ -0,0 +1,801 @@
+/* { dg-options "--coverage" } */
+/* { dg-do run { target native } } */
+
+/* testsuite/gcc.misc-tests/gcov-35.c, compiled with the C++ frontend. */
+
+void noop () {}
+
+int do_something (int i) { return i; }
+
+/* Empty loop bodies should still disable coverage for the for (;;) and compile
+ fine. */
+int
+empty_body_for_loop ()
+{
+ int i;
+ __attribute__((disable_coverage))
+ for (i = 0; i < 10; i++) /* count(#) */
+ ;
+ return i;
+}
+
+/* Ignoring the loop should not ignore the return, but ignore everything within
+ the loop. */
+int
+ignored_for_loop ()
+{
+ int i;
+ __attribute__((disable_coverage))
+ for (i = 0; i < 10; i++) /* count(#) */
+ {
+ noop (); /* count(#) */
+ noop (); /* count(#) */
+ }
+
+ /* Making the for (;;) multi line should report count per line. g++
considers the i++ unexecuted, while gcc counts it. */
+ __attribute__((disable_coverage))
+ for (i = 0; /* count(#) */
+ i < 20; /* count(#) */
+ i++) /* count(-) */
+ {
+ noop (); /* count(#) */
+ }
+
+ noop ();
+ i++;
+
+ return 0; /* count(1) */
+}
+
+int
+declarations (int a)
+{
+ // Should work when it is the first declaration
+ __attribute__((disable_coverage))
+ int b = a + 1; /* count(#) */
+
+ // This is ignored in C (no init) but may be implicitly initialized in C++ to
+ // an erroneous value, so this could be - (not executed) or # (ignored)
+ // depending on the -std= flag.
+ __attribute__((disable_coverage))
+ int c;
+
+ a *= 2; /* count(1) */
+
+ // Should work when it is not the first declaration
+ __attribute__((disable_coverage))
+ int d = a - 1; /* count(#) */
+
+ c = a+b+d; /* count(1) */
+ return c;
+}
+
+int
+compound_statements (int a)
+{
+ int c;
+ __attribute__((disable_coverage))
+ {
+ int b = a + 1; /* count(#) */
+ a *= 2; /* count(#) */
+ int d = a - 1; /* count(#) */
+ c = a+b+d; /* count(#) */
+ }
+ return c; /* count(1) */
+}
+
+int
+while1 (int a)
+{
+ __attribute__((disable_coverage))
+ while (a > 0) /* count(#) */
+ a = do_something (a - 1); /* count(#) */
+ return a;
+}
+
+int
+while2 (int a)
+{
+ while (a > 0) /* count(6) */
+ __attribute__((disable_coverage))
+ a = do_something (a - 1); /* count(#) */
+ return a;
+}
+
+int
+dowhile1 (int a)
+{
+ __attribute__((disable_coverage))
+ do
+ {
+ a = do_something (a - 1); /* count(#) */
+ } while (a > 0); /* count(#) */
+ return a;
+}
+
+int
+dowhile2 (int a)
+{
+ do
+ {
+ __attribute__((disable_coverage))
+ a = do_something (a - 1); /* count(#) */
+ } while (a > 0); /* count(5) */
+ return a;
+}
+
+void
+call_while ()
+{
+ while1 (5);
+ while2 (5);
+ dowhile1 (5);
+ dowhile2 (5);
+}
+
+/* Based on gcov-pr85217.c, a loop with both breaks and continues. */
+int
+for1 ()
+{
+ int a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ break; /* count(1) */
+ a; /* count(-) */
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ __attribute__((disable_coverage))
+ for (;; a++) /* count(#) */
+ {
+ int c[1];
+ if (a) /* count(#) */
+ {
+ break; /* count(#) */
+ a; /* count(-) */
+ continue; /* count(#) */
+ }
+ continue; /* count(#) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ __attribute__((disable_coverage))
+ if (a) /* count(#) */
+ {
+ break; /* count(#) */
+ a; /* count(-) */
+ /* This continue is merged into the continue outside of the if and is
+ associated with that block. It is unreachable through break, but
+ gets its count from that. */
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ __attribute__((disable_coverage))
+ break; /* count(#) */
+ a; /* count(-) */
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ break; /* count(1) */
+ a; /* count(-) */
+ __attribute__((disable_coverage))
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ break; /* count(1) */
+ a; /* count(-) */
+ continue; /* count(1) */
+ }
+ __attribute__((disable_coverage))
+ continue; /* count(#) */
+ }
+
+ return a;
+}
+
+/* Based on the test in gcov-4.c */
+int for_val1;
+int for_temp;
+int
+nested_for1 (int m, int n, int o)
+{
+ int i, j, k;
+ for_temp = 1; /* count(6) */
+ for (i = 0; i < n; i++) /* count(20) */
+ for (j = 0; j < m; j++) /* count(44) */
+ __attribute__((disable_coverage))
+ for (k = 0; k < o; k++) /* count(#) */
+ for_temp++; /* count(#) */
+ return for_temp; /* count(6) */
+}
+
+/* A loop with break. */
+int
+for2 (int n)
+{
+ int acc = 0;
+ for (int i = 0; i < n; ++i) /* count(7) */
+ {
+ acc += do_something (i); /* count(7) */
+ __attribute__((disable_coverage))
+ if (acc > 10) /* count(#) */
+ break; /* count(#) */
+ acc -= 1; /* count(6) */
+ }
+ return acc;
+}
+
+int
+for3 (int n)
+{
+ int acc = 0;
+ for (int i = 0; i < n; ++i) /* count(7) */
+ {
+ acc += do_something (i); /* count(7) */
+ if (acc > 10) /* count(7) */
+ __attribute__((disable_coverage))
+ break; /* count(#) */
+ acc -= 1; /* count(6) */
+ }
+ return acc;
+}
+
+void
+call_for ()
+{
+ for1 ();
+ for2 (10);
+ for3 (10);
+
+ for_val1 += nested_for1 (0, 0, 0);
+ for_val1 += nested_for1 (1, 0, 0);
+ for_val1 += nested_for1 (1, 3, 0);
+ for_val1 += nested_for1 (1, 3, 1);
+ for_val1 += nested_for1 (3, 1, 5);
+ for_val1 += nested_for1 (3, 7, 3);
+}
+
+int ifelse_val1;
+int ifelse_val2;
+int ifelse_val3;
+
+int
+test_ifelse1 (int i, int j)
+{
+ int result = 0;
+ /* We can ignore the THEN. */
+ if (i) /* count(5) */
+ if (j) /* count(3) */
+ __attribute__((disable_coverage))
+ result = do_something (4); /* count(#) */
+ else
+ result = do_something (1024);
+ /* We can ignore the ELSE. */
+ else
+ if (j) /* count(2) */
+ result = do_something (1); /* count(1) */
+ else
+ __attribute__((disable_coverage))
+ result = do_something (2); /* count(#) */
+ if (i > j) /* count(5) */
+ result = do_something (result*2); /* count(1) */
+
+ /* We can ignore the whole if-then-else. */
+ if (i > 10) /* count(5) */
+ __attribute__((disable_coverage))
+ if (j > 10) /* count(#) */
+ result = do_something (result*4); /* count(#) */
+ return result; /* count(5) */
+}
+
+int
+test_ifelse2 (int i)
+{
+ int result = 0;
+ __attribute__((disable_coverage))
+ if (!i) /* count(#) */
+ result = do_something (1); /* count(#) */
+
+ if (i == 1) /* count(6) */
+ result = do_something (1024);
+
+ if (i == 2) /* count(6) */
+ __attribute__((disable_coverage))
+ result = do_something (2); /* count(#) */
+
+ if (i == 3) /* count(6) */
+ __attribute__((disable_coverage))
+ return do_something (8); /* count(#) */
+
+ __attribute__((disable_coverage))
+ if (i == 4) /* count(#) */
+ return do_something (2048); /* count(#) */
+
+ return result; /* count(4) */
+}
+
+int
+test_ifelse3 (int i, int j)
+{
+ int result = 1;
+ /* Multi-condition ifs are disabled, too */
+ __attribute__((disable_coverage))
+ if (i > 10 && j > i && j < 20) /* count(#) */
+ result = do_something (16); /* count(#) */
+
+ if (i == 3 || j == 47 || i == j) /* count(11) */
+ __attribute__((disable_coverage))
+ result = do_something (64); /* count(#) */
+
+ return result; /* count(11) */
+}
+
+/* These are based on gcov-17.c */
+int
+test_ifelse4 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ __attribute__((disable_coverage))
+ if (true_var) /* count(#) */
+ {
+ if (false_var) /* count(#) */
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#) */
+ return ret;
+}
+
+int
+test_ifelse5 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ __attribute__((disable_coverage))
+ {
+ if (false_var) /* count(#) */
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#####) */
+ return ret;
+}
+
+int
+test_ifelse6 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ {
+ __attribute__((disable_coverage))
+ if (false_var) /* count(#) */
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#####) */
+ return ret;
+}
+
+int
+test_ifelse7 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ {
+ if (false_var) /* count(1) */
+ __attribute__((disable_coverage))
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#####) */
+ return ret;
+}
+
+int
+test_ifelse8 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ {
+ if (false_var) /* count(1) */
+ ret = 111; /* count(#####) */
+ }
+ else
+ __attribute__((disable_coverage))
+ ret = 999; /* count(#) */
+ return ret;
+}
+
+void
+call_ifelse ()
+{
+ ifelse_val1 += test_ifelse1 (0, 2);
+ ifelse_val1 += test_ifelse1 (0, 0);
+ ifelse_val1 += test_ifelse1 (1, 2);
+ ifelse_val1 += test_ifelse1 (10, 2);
+ ifelse_val1 += test_ifelse1 (11, 11);
+
+ ifelse_val2 += test_ifelse2 (0);
+ ifelse_val2 += test_ifelse2 (2);
+ ifelse_val2 += test_ifelse2 (2);
+ ifelse_val2 += test_ifelse2 (2);
+ ifelse_val2 += test_ifelse2 (3);
+ ifelse_val2 += test_ifelse2 (3);
+
+ ifelse_val3 += test_ifelse3 (11, 19);
+ ifelse_val3 += test_ifelse3 (25, 27);
+ ifelse_val3 += test_ifelse3 (11, 22);
+ ifelse_val3 += test_ifelse3 (11, 10);
+ ifelse_val3 += test_ifelse3 (21, 32);
+ ifelse_val3 += test_ifelse3 (21, 20);
+ ifelse_val3 += test_ifelse3 (1, 2);
+ ifelse_val3 += test_ifelse3 (32, 31);
+ ifelse_val3 += test_ifelse3 (3, 0);
+ ifelse_val3 += test_ifelse3 (0, 47);
+ ifelse_val3 += test_ifelse3 (65, 65);
+
+ test_ifelse4 (1, 0);
+ test_ifelse5 (1, 0);
+ test_ifelse6 (1, 0);
+ test_ifelse7 (1, 0);
+ test_ifelse8 (1, 0);
+}
+
+int switch_val, switch_m;
+int
+test_switch (int i, int j)
+{
+ int result = 0; /* count(5) */
+ /* We can disable individual statements and breaks in the switch. */
+ switch (i) /* count(5) */
+ {
+ case 1:
+ __attribute__((disable_coverage))
+ result = do_something (2); /* count(#) */
+ break; /* count(1) */
+ case 2:
+ result = do_something (1024);
+ break;
+ case 3:
+ case 4:
+ if (j == 2) /* count(3) */
+ return do_something (4); /* count(1) */
+ result = do_something (8); /* count(2) */
+ __attribute__((disable_coverage))
+ break; /* count(#) */
+ default:
+ result = do_something (32); /* count(1) */
+ __attribute__((disable_coverage))
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ /* We can disable the whole switch. */
+ __attribute__((disable_coverage))
+ switch (i)
+ {
+ case 1:
+ result = do_something (64); /* count(#) */
+ break; /* count(#) */
+ case 2:
+ result = do_something (128); /* count(#) */
+ break; /* count(#) */
+ case 3:
+ result = do_something (256); /* count(#) */
+ break; /* count(#) */
+ default:
+ result = do_something (512); /* count(#) */
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ return result; /* count(4) */
+}
+
+void
+call_switch ()
+{
+ switch_val += test_switch (1, 0);
+ switch_val += test_switch (3, 0);
+ switch_val += test_switch (3, 2);
+ switch_val += test_switch (4, 0);
+ switch_val += test_switch (16, 0);
+ switch_val += switch_m;
+}
+
+/* The goto tests from gcov-4.c. */
+int goto_val;
+
+int
+test_goto1 (int f)
+{
+ __attribute__((disable_coverage))
+ if (f) /* count(#) */
+ goto lab1; /* count(#) */
+ return 1; /* count(1) */
+lab1:
+ return 2; /* count(1) */
+}
+
+int
+test_goto2 (int f)
+{
+ int i;
+ for (i = 0; i < 10; i++) /* count(15) */
+ if (i == f) goto lab2; /* count(14) */
+ return 4; /* count(1) */
+lab2:
+ // Add an empty statement so the attribute is applied to the return, not the
+ // label.
+ ;
+ __attribute__((disable_coverage))
+ return 8; /* count(#) */
+}
+
+int
+test_goto3 (int i, int j)
+{
+ if (j) /* count(1) */
+ __attribute__((disable_coverage))
+ goto else_; /* count(#) */
+
+top:
+ if (i) /* count(1) */
+ {
+ i = do_something (i);
+ }
+ else
+ {
+else_: /* count(1) */
+ j = do_something (j); /* count(2) */
+ __attribute__((disable_coverage))
+ if (j) /* count(#) */
+ {
+ j = 0; /* count(#) */
+ goto top; /* count(#) */
+ }
+ }
+ return 16; /* count(1) */
+}
+
+/* Not from gcov-4.c */
+int
+test_goto4 (int f, int g)
+{
+ /* The attribute should apply to all statements inside the {}, even the goto
+ when the label is inside the disabled block. When jumping out, the
+ destination should still be counted. */
+ __attribute__((disable_coverage))
+ {
+ if (f) /* count(#) */
+ goto inside; /* count(#) */
+ if (g) /* count(#) */
+ goto outside; /* count(#) */
+
+ inside:
+ if (g) /* count(#) */
+ goto skip; /* count(#) */
+ f += 2; /* count(#) */
+
+ skip:
+ g += 2; /* count(#) */
+ }
+ return 1; /* count(3) */
+outside:
+ return 2; /* count(1) */
+}
+
+/* Based on gcov-18.c */
+int
+test_goto5 (int a)
+{
+ /* If just one statement is ignored, the whole line should be. */
+ __attribute__((disable_coverage))
+ noop (); goto baz; lab: a = do_something (a+1); /* count(#) */
+ baz:
+ if (a == 1) /* count(2) */
+ goto lab; /* count(1) */
+ return a;
+}
+
+void
+call_goto ()
+{
+ goto_val += test_goto1 (0);
+ goto_val += test_goto1 (1);
+ goto_val += test_goto2 (3);
+ goto_val += test_goto2 (30);
+ goto_val += test_goto3 (0, 1);
+
+ goto_val += test_goto4 (0, 0);
+ goto_val += test_goto4 (0, 1);
+ goto_val += test_goto4 (1, 0);
+ goto_val += test_goto4 (1, 1);
+
+ goto_val += test_goto5 (1);
+}
+
+/* Returns, guarded by both plain values and function calls. */
+void
+return1 (int a, int b, int c)
+{
+ __attribute__((disable_coverage))
+ if (a) return; /* count(#) */
+ __attribute__((disable_coverage))
+ if (do_something (b)) return; /* count(#) */
+ if (do_something (c)) /* count(1) */
+ __attribute__((disable_coverage)) return; /* count(#) */
+}
+
+void
+call_return ()
+{
+ return1 (1, 0, 0);
+ return1 (0, 1, 0);
+ return1 (0, 0, 1);
+}
+
+/* From gcov-6.c */
+extern "C" void exit (int);
+int test_exit_val;
+
+void
+test_exit1 (int i)
+{
+ /* An abnormal exit should not break suppression. */
+ __attribute__((disable_coverage))
+ if (i < 0) /* count(#) */
+ exit (0); /* count(#) */
+ test_exit_val += i; /* count(3) */
+}
+
+void
+test_exit2 (int i)
+{
+ if (i < 0) /* count(4) */
+ __attribute__((disable_coverage))
+ exit (0); /* count(#) */
+ test_exit_val += i; /* count(3) */
+}
+
+void
+test_exit3 (int i)
+{
+ /* There can be statements on either side of exit (). */
+ __attribute__((disable_coverage))
+ if (i < 0) /* count(#) */
+ {
+ test_exit_val += i; /* count(#) */
+ exit (0); /* count(#) */
+ test_exit_val += i; /* count(-) */
+ }
+ test_exit_val += i; /* count(3) */
+}
+
+void
+call_exit ()
+{
+ for (int i = 0; i != 3; ++i)
+ test_exit1 (i);
+ for (int i = 0; i != 3; ++i)
+ test_exit2 (i);
+ for (int i = 0; i != 3; ++i)
+ test_exit3 (i);
+
+ test_exit2 (-1);
+}
+
+int
+computed_goto1 (int a)
+{
+ /* __attribute__((disable_coverage))
+ void *op = &&dest;
+
+ is parsed as an attribute on the declaration void *op, not the assignment.
+ We can "fix" this by splitting the declaration and assignment. */
+ void *op;
+ __attribute__((disable_coverage))
+ op = &&dest; /* count(#) */
+dest:
+ if (op && a > 0) /* count(6) */
+ {
+ a -= 1; /* count(5) */
+ goto *op; /* count(5) */
+ }
+
+ return a;
+}
+
+int
+computed_goto2 (int a)
+{
+ void *op = &&dest; /* count(1) */
+dest:
+ ;
+ __attribute__((disable_coverage))
+ if (op && a > 0) /* count(#) */
+ {
+ a -= 1; /* count(#) */
+ goto *op; /* count(#) */
+ }
+
+ return a;
+}
+
+int
+computed_goto3 (int a)
+{
+ void *op = &&dest; /* count(1) */
+dest:
+ ;
+ if (op && a > 0) /* count(6) */
+ {
+ a -= 1; /* count(5) */
+ __attribute__((disable_coverage))
+ goto *op; /* count(#) */
+ }
+
+ return a;
+}
+
+void
+call_computed_goto ()
+{
+ computed_goto1 (5);
+ computed_goto2 (5);
+ computed_goto3 (5);
+}
+
+int main ()
+{
+ empty_body_for_loop ();
+ ignored_for_loop ();
+ declarations (1);
+ compound_statements (1);
+ call_while ();
+ call_for ();
+ call_ifelse ();
+ call_switch ();
+ call_goto ();
+ call_return ();
+ call_computed_goto ();
+
+ /* The final test will actually exit, so make sure to call it last. */
+ call_exit ();
+ return 0;
+}
+
+/* { dg-final { run-gcov gcov-24.C } } */
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-25.C
b/gcc/testsuite/g++.dg/gcov/gcov-25.C
new file mode 100644
index 00000000000..059013b328a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-25.C
@@ -0,0 +1,98 @@
+/* { dg-options "--coverage" } */
+/* { dg-do run { target native } } */
+
+/* This file contains a select few of the tests in gcov-24.C, but uses the
+ [[attribute]] syntax instead of __attribute__((attr)). */
+
+int do_something (int x) { return x; }
+
+int
+empty_body_for_loop ()
+{
+ int i;
+ [[gnu::disable_coverage]]
+ for (i = 0; i < 10; i++) /* count(#) */
+ ;
+ return i;
+}
+
+void
+return1 (int a, int b, int c)
+{
+ [[gnu::disable_coverage]]
+ if (a) return; /* count(#) */
+ [[gnu::disable_coverage]]
+ if (do_something (b)) return; /* count(#) */
+ if (do_something (c)) /* count(1) */
+ [[gnu::disable_coverage]]
+ return; /* count(#) */
+}
+
+int
+test_switch (int i, int j)
+{
+ int result = 0; /* count(5) */
+ int switch_m = 0;
+ /* We can disable individual statements and breaks in the switch. */
+ switch (i) /* count(5) */
+ {
+ case 1:
+ [[gnu::disable_coverage]]
+ result = do_something (2); /* count(#) */
+ break; /* count(1) */
+ case 2:
+ result = do_something (1024);
+ break;
+ case 3:
+ case 4:
+ if (j == 2) /* count(3) */
+ return do_something (4); /* count(1) */
+ result = do_something (8); /* count(2) */
+ [[gnu::disable_coverage]]
+ break; /* count(#) */
+ default:
+ result = do_something (32); /* count(1) */
+ [[gnu::disable_coverage]]
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ /* We can disable the whole switch. */
+ [[gnu::disable_coverage]]
+ switch (i)
+ {
+ case 1:
+ result = do_something (64); /* count(#) */
+ break; /* count(#) */
+ case 2:
+ result = do_something (128); /* count(#) */
+ break; /* count(#) */
+ case 3:
+ result = do_something (256); /* count(#) */
+ break; /* count(#) */
+ default:
+ result = do_something (512); /* count(#) */
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ return result; /* count(4) */
+}
+
+int
+main ()
+{
+ empty_body_for_loop ();
+
+ return1 (1, 0, 0);
+ return1 (0, 1, 0);
+ return1 (0, 0, 1);
+
+ test_switch (1, 0);
+ test_switch (3, 0);
+ test_switch (3, 2);
+ test_switch (4, 0);
+ test_switch (16, 0);
+}
+
+/* { dg-final { run-gcov gcov-25.C } } */
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-26.C
b/gcc/testsuite/g++.dg/gcov/gcov-26.C
new file mode 100644
index 00000000000..d4efa660eef
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-26.C
@@ -0,0 +1,325 @@
+/* A collection of C++ specific constructs with coverage disabled. */
+
+/* { dg-options "--coverage" } */
+/* { dg-do run { target native } } */
+
+#include <stdexcept>
+
+void noop () {}
+void noop (int) {}
+
+void throws (int i)
+{
+ if (i)
+ throw 1;
+}
+
+void throws_stdexcept (int i)
+{
+ switch (i)
+ {
+ case 1: throw std::length_error("length error");
+ case 2: throw std::domain_error("domain error");
+ case 3: throw std::runtime_error("runtime error");
+ default: return;
+ }
+}
+
+
+/* We can disable coverage for statements inside try/catch blocks. */
+void try_catch1 ()
+{
+ try
+ {
+ [[gnu::disable_coverage]]
+ throws (0); /* count(#) */
+ throws (0); /* count(1) */
+ }
+ catch (...)
+ {
+ noop (); /* count(=====) */
+ }
+
+ try
+ {
+ throws (1); /* count(1) */
+ throws (0); /* count(#####) */
+ }
+ catch (...)
+ {
+ noop (); /* count (1) */
+ }
+}
+
+void try_catch2 ()
+{
+ try
+ {
+ throws (0); /* count(1) */
+ [[gnu::disable_coverage]]
+ throws (0); /* count(#) */
+ }
+ catch (...)
+ {
+ noop (); /* count(=====) */
+ }
+
+ try
+ {
+ throws (1); /* count(1) */
+ throws (0); /* count(#####) */
+ }
+ catch (...)
+ {
+ noop (); /* count (1) */
+ }
+}
+
+void try_catch3 ()
+{
+ try
+ {
+ throws (0); /* count(1) */
+ throws (0); /* count(1) */
+ }
+ catch (...)
+ {
+ noop (); /* count(=====) */
+ }
+
+ try
+ {
+ throws (1); /* count(1) */
+ throws (0); /* count(#####) */
+ }
+ catch (...)
+ {
+ [[gnu::disable_coverage]]
+ noop (); /* count (#) */
+ }
+}
+
+/* We can disable try-catch altogether. */
+void try_catch4 ()
+{
+ [[gnu::disable_coverage]]
+ try
+ {
+ throws (0); /* count(#) */
+ throws (0); /* count(#) */
+ }
+ catch (...)
+ {
+ noop (); /* count(#) */
+ }
+
+ try
+ {
+ throws (1); /* count(1) */
+ throws (0); /* count(#####) */
+ }
+ catch (...)
+ {
+ noop (); /* count (#) */
+ }
+}
+
+void try_catch5 ()
+{
+ try
+ {
+ throws (0); /* count(1) */
+ throws (0); /* count(1) */
+ }
+ catch (...)
+ {
+ noop (); /* count(=====) */
+ }
+
+ [[gnu::disable_coverage]]
+ try
+ {
+ throws (1); /* count(#) */
+ throws (0); /* count(#) */
+ }
+ catch (...)
+ {
+ noop (); /* count (#) */
+ }
+}
+
+void try_catch6 ()
+{
+ try
+ {
+ throws (0); /* count(1) */
+ throws (0); /* count(1) */
+ }
+ catch (...)
+ {
+ noop (); /* count(=====) */
+ }
+
+ try
+ {
+ throws (1); /* count(1) */
+ throws (0); /* count(#####) */
+ }
+ catch (...)
+ {
+ [[gnu::disable_coverage]]
+ {
+ noop (); /* count (#) */
+ noop (); /* count (#) */
+ }
+ }
+}
+
+void try_catch7 ()
+{
+ [[gnu::disable_coverage]]
+ try
+ {
+ throws (0); /* count(#) */
+ throws (0); /* count(#) */
+ }
+ catch (...)
+ {
+ noop (); /* count(#) */
+ }
+}
+
+void try_catch8 ()
+{
+ [[gnu::disable_coverage]]
+ try
+ {
+ throws_stdexcept (0); /* count(#) */
+ throws_stdexcept (1); /* count(#) */
+ }
+ catch (std::length_error&)
+ {
+ noop (1); /* count(#) */
+ }
+ catch (std::domain_error&)
+ {
+ noop (2); /* count(#) */
+ }
+ catch (std::runtime_error&)
+ {
+ noop (3); /* count(#) */
+ }
+
+ try
+ {
+ throws_stdexcept (1); /* count(1) */
+ throws_stdexcept (0); /* count(#####) */
+ }
+ catch (std::length_error&)
+ {
+ [[gnu::disable_coverage]]
+ noop (4); /* count(#) */
+ }
+ catch (std::domain_error&)
+ {
+ noop (5); /* count(=====) */
+ }
+ catch (std::runtime_error&)
+ {
+ noop (6); /* count(=====) */
+ }
+
+ try
+ {
+ throws_stdexcept (2); /* count(1) */
+ throws_stdexcept (0); /* count(#####) */
+ }
+ catch (std::length_error&)
+ {
+ noop (7); /* count(=====) */
+ }
+ catch (std::domain_error&)
+ {
+ [[gnu::disable_coverage]]
+ noop (8); /* count(#) */
+ }
+ catch (std::runtime_error&)
+ {
+ noop (9); /* count(=====) */
+ }
+}
+
+/* Throws are disabled, either directly or through its surrounding block. */
+int ifelse_throw1 (int f)
+{
+ [[gnu::disable_coverage]]
+ if (f >= 2) /* count(#) */
+ throw 1; /* count(#) */
+
+ return f; /* count(1) */
+}
+
+int ifelse_throw2 (int f)
+{
+ if (f >= 2) /* count(2) */
+ [[gnu::disable_coverage]]
+ throw 1; /* count(#) */
+
+ return f; /* count(1) */
+}
+
+int ctor_x;
+/* Disabling coverage for a default constructor/initialization. */
+void ctor1 ()
+{
+ class C
+ {
+ int v;
+ public:
+ C() : v(5) {}
+ };
+
+ [[gnu::disable_coverage]]
+ C c; /* count(#) */
+ // arbitrary action between ctor+dtor
+ ctor_x = 1; /* count(1) */
+}
+
+/* Disabling coverage for a constructor/initialization with args. */
+void ctor2 (int a)
+{
+ class C
+ {
+ public:
+ explicit C (int e) : v (e) {}
+ int v;
+ };
+
+ [[gnu::disable_coverage]]
+ C c (a); /* count(#) */
+ // arbitrary action between ctor+dtor
+ ctor_x = 1; /* count(1) */
+}
+
+int main ()
+{
+ try_catch1 ();
+ try_catch2 ();
+ try_catch3 ();
+ try_catch4 ();
+ try_catch5 ();
+ try_catch6 ();
+ try_catch7 ();
+ try_catch8 ();
+
+ try { ifelse_throw1 (1); } catch (...) {}
+ try { ifelse_throw1 (2); } catch (...) {}
+
+ try { ifelse_throw2 (1); } catch (...) {}
+ try { ifelse_throw2 (2); } catch (...) {}
+
+ ctor1 ();
+ ctor2 (5);
+}
+
+/* { dg-final { run-gcov gcov-26.C } } */
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-27.C
b/gcc/testsuite/g++.dg/gcov/gcov-27.C
new file mode 100644
index 00000000000..67f74cd7cfb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-27.C
@@ -0,0 +1,32 @@
+/* { dg-require-effective-target c++11 } */
+/* { dg-options "--coverage -std=c++11" } */
+/* { dg-do run { target native } } */
+
+void lambda1 ()
+{
+ /* From pr86109.C */
+ auto partially_uncovered_lambda1 = [](int i) { /* count(1) */
+ [[gnu::disable_coverage]]
+ if (i > 10) /* count(#) */
+ return 0; /* count(#) */
+ return 1; /* count(#####) */
+ };
+
+ auto partially_uncovered_lambda2 = [](int i) { /* count(1) */
+ if (i > 10) /* count(1) */
+ [[gnu::disable_coverage]]
+ return 0; /* count(#) */
+ return 1; /* count(#####) */
+ };
+
+ partially_uncovered_lambda1 (20);
+ partially_uncovered_lambda2 (20);
+}
+
+
+int main ()
+{
+ lambda1 ();
+}
+
+/* { dg-final { run-gcov gcov-27.C } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-35.c
b/gcc/testsuite/gcc.misc-tests/gcov-35.c
new file mode 100644
index 00000000000..3e375277a02
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-35.c
@@ -0,0 +1,834 @@
+/* { dg-options "--coverage" } */
+/* { dg-do run { target native } } */
+
+extern void exit (int);
+
+void noop () {}
+
+int do_something (int i) { return i; }
+
+/* Empty loop bodies should still disable coverage for the for (;;) and compile
+ fine. */
+int
+empty_body_for_loop ()
+{
+ int i;
+ __attribute__((disable_coverage))
+ for (i = 0; i < 10; i++) /* count(#) */
+ ;
+ return i;
+}
+
+/* Ignoring the loop should not ignore the return, but ignore everything within
+ the loop. */
+int
+ignored_for_loop ()
+{
+ int i;
+ __attribute__((disable_coverage))
+ for (i = 0; i < 10; i++) /* count(#) */
+ {
+ noop (); /* count(#) */
+ noop (); /* count(#) */
+ }
+
+ /* Making the for (;;) multi line should report count per line. */
+ __attribute__((disable_coverage))
+ for (i = 0; /* count(#) */
+ i < 20; /* count(#) */
+ i++) /* count(#) */
+ {
+ noop (); /* count(#) */
+ }
+
+ noop ();
+ i++;
+
+ return 0; /* count(1) */
+}
+
+int
+declarations (int a)
+{
+ // Should work when it is the first declaration
+ __attribute__((disable_coverage))
+ int b = a + 1; /* count(#) */
+
+ // declarations with no init has no, and disable_coverage is a no-op
+ __attribute__((disable_coverage))
+ int c; /* count(-) */
+
+ a *= 2; /* count(1) */
+
+ // Should work when it is not the first declaration
+ __attribute__((disable_coverage))
+ int d = a - 1; /* count(#) */
+
+ c = a+b+d; /* count(1) */
+ return c;
+}
+
+int
+compound_statements (int a)
+{
+ int c; /* count(-) */
+ __attribute__((disable_coverage))
+ {
+ int b = a + 1; /* count(#) */
+ a *= 2; /* count(#) */
+ int d = a - 1; /* count(#) */
+ c = a+b+d; /* count(#) */
+ }
+ return c; /* count(1) */
+}
+
+int
+while1 (int a)
+{
+ __attribute__((disable_coverage))
+ while (a > 0) /* count(#) */
+ a = do_something (a - 1); /* count(#) */
+ return a;
+}
+
+int
+while2 (int a)
+{
+ while (a > 0) /* count(6) */
+ __attribute__((disable_coverage))
+ a = do_something (a - 1); /* count(#) */
+ return a;
+}
+
+int
+dowhile1 (int a)
+{
+ __attribute__((disable_coverage))
+ do
+ {
+ a = do_something (a - 1); /* count(#) */
+ } while (a > 0); /* count(#) */
+ return a;
+}
+
+int
+dowhile2 (int a)
+{
+ do
+ {
+ __attribute__((disable_coverage))
+ a = do_something (a - 1); /* count(#) */
+ } while (a > 0); /* count(5) */
+ return a;
+}
+
+void
+call_while ()
+{
+ while1 (5);
+ while2 (5);
+ dowhile1 (5);
+ dowhile2 (5);
+}
+
+/* Based on gcov-pr85217.c, a loop with both breaks and continues. */
+int
+for1 ()
+{
+ int a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ break; /* count(1) */
+ a; /* count(-) */
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ __attribute__((disable_coverage))
+ for (;; a++) /* count(#) */
+ {
+ int c[1];
+ if (a) /* count(#) */
+ {
+ break; /* count(#) */
+ a; /* count(-) */
+ continue; /* count(#) */
+ }
+ continue; /* count(#) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ __attribute__((disable_coverage))
+ if (a) /* count(#) */
+ {
+ break; /* count(#) */
+ a; /* count(-) */
+ /* This continue is merged into the continue outside of the if and is
+ associated with that block. It is unreachable through break, but
+ gets its count from that. */
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ __attribute__((disable_coverage))
+ break; /* count(#) */
+ a; /* count(-) */
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ break; /* count(1) */
+ a; /* count(-) */
+ __attribute__((disable_coverage))
+ continue; /* count(1) */
+ }
+ continue; /* count(1) */
+ }
+
+ a = 0;
+ for (;; a++) /* count(1) */
+ {
+ int c[1];
+ if (a) /* count(2) */
+ {
+ break; /* count(1) */
+ a; /* count(-) */
+ continue; /* count(1) */
+ }
+ __attribute__((disable_coverage))
+ continue; /* count(#) */
+ }
+
+ return a;
+}
+
+/* A loop with break. */
+int
+for2 (int n)
+{
+ int acc = 0;
+ for (int i = 0; i < n; ++i) /* count(7) */
+ {
+ acc += do_something (i); /* count(7) */
+ __attribute__((disable_coverage))
+ if (acc > 10) /* count(#) */
+ break; /* count(#) */
+ acc -= 1; /* count(6) */
+ }
+ return acc;
+}
+
+int
+for3 (int n)
+{
+ int acc = 0;
+ for (int i = 0; i < n; ++i) /* count(7) */
+ {
+ acc += do_something (i); /* count(7) */
+ if (acc > 10) /* count(7) */
+ /* The block/line gets anchored to the attribute, not the break, but
+ the data is fine. */
+ __attribute__((disable_coverage))
+ break; /* count(-) */
+ acc -= 1; /* count(6) */
+ }
+ return acc;
+}
+
+/* Based on the test in gcov-4.c */
+int for_val1;
+int for_temp;
+int
+nested_for1 (int m, int n, int o)
+{
+ int i, j, k;
+ for_temp = 1; /* count(6) */
+ for (i = 0; i < n; i++) /* count(20) */
+ for (j = 0; j < m; j++) /* count(44) */
+ __attribute__((disable_coverage))
+ for (k = 0; k < o; k++) /* count(#) */
+ for_temp++; /* count(#) */
+ return for_temp; /* count(6) */
+}
+
+void
+call_for ()
+{
+ for1 ();
+ for2 (10);
+ for3 (10);
+
+ for_val1 += nested_for1 (0, 0, 0);
+ for_val1 += nested_for1 (1, 0, 0);
+ for_val1 += nested_for1 (1, 3, 0);
+ for_val1 += nested_for1 (1, 3, 1);
+ for_val1 += nested_for1 (3, 1, 5);
+ for_val1 += nested_for1 (3, 7, 3);
+}
+
+int ifelse_val1;
+int ifelse_val2;
+int ifelse_val3;
+
+int
+test_ifelse1 (int i, int j)
+{
+ int result = 0;
+ /* We can ignore the THEN. */
+ if (i) /* count(5) */
+ if (j) /* count(3) */
+ __attribute__((disable_coverage))
+ result = do_something (4); /* count(#) */
+ else
+ result = do_something (1024);
+ /* We can ignore the ELSE. */
+ else
+ if (j) /* count(2) */
+ result = do_something (1); /* count(1) */
+ else
+ __attribute__((disable_coverage))
+ result = do_something (2); /* count(#) */
+ if (i > j) /* count(5) */
+ result = do_something (result*2); /* count(1) */
+
+ /* We can ignore the whole if-then-else. */
+ if (i > 10) /* count(5) */
+ __attribute__((disable_coverage))
+ if (j > 10) /* count(#) */
+ result = do_something (result*4); /* count(#) */
+ return result; /* count(5) */
+}
+
+int
+test_ifelse2 (int i)
+{
+ int result = 0;
+ __attribute__((disable_coverage))
+ if (!i) /* count(#) */
+ result = do_something (1); /* count(#) */
+
+ if (i == 1) /* count(6) */
+ result = do_something (1024);
+
+ if (i == 2) /* count(6) */
+ __attribute__((disable_coverage))
+ result = do_something (2); /* count(#) */
+
+ if (i == 3) /* count(6) */
+ __attribute__((disable_coverage))
+ return do_something (8); /* count(#) */
+
+ __attribute__((disable_coverage))
+ if (i == 4) /* count(#) */
+ return do_something (2048); /* count(#) */
+
+ return result; /* count(4) */
+}
+
+int
+test_ifelse3 (int i, int j)
+{
+ int result = 1;
+ /* Multi-condition ifs are disabled, too */
+ __attribute__((disable_coverage))
+ if (i > 10 && j > i && j < 20) /* count(#) */
+ result = do_something (16); /* count(#) */
+
+ if (i == 3 || j == 47 || i == j) /* count(11) */
+ __attribute__((disable_coverage))
+ result = do_something (64); /* count(#) */
+
+ return result; /* count(11) */
+}
+
+/* These are based on gcov-17.c */
+int
+test_ifelse4 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ __attribute__((disable_coverage))
+ if (true_var) /* count(#) */
+ {
+ if (false_var) /* count(#) */
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#) */
+ return ret;
+}
+
+int
+test_ifelse5 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ __attribute__((disable_coverage))
+ {
+ if (false_var) /* count(#) */
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#####) */
+ return ret;
+}
+
+int
+test_ifelse6 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ {
+ __attribute__((disable_coverage))
+ if (false_var) /* count(#) */
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#####) */
+ return ret;
+}
+
+int
+test_ifelse7 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ {
+ if (false_var) /* count(1) */
+ __attribute__((disable_coverage))
+ ret = 111; /* count(#) */
+ }
+ else
+ ret = 999; /* count(#####) */
+ return ret;
+}
+
+int
+test_ifelse8 (int true_var, int false_var)
+{
+ unsigned int ret = 0;
+ if (true_var) /* count(1) */
+ {
+ if (false_var) /* count(1) */
+ ret = 111; /* count(#####) */
+ }
+ else
+ __attribute__((disable_coverage))
+ ret = 999; /* count(#) */
+ return ret;
+}
+
+void
+call_ifelse ()
+{
+ ifelse_val1 += test_ifelse1 (0, 2);
+ ifelse_val1 += test_ifelse1 (0, 0);
+ ifelse_val1 += test_ifelse1 (1, 2);
+ ifelse_val1 += test_ifelse1 (10, 2);
+ ifelse_val1 += test_ifelse1 (11, 11);
+
+ ifelse_val2 += test_ifelse2 (0);
+ ifelse_val2 += test_ifelse2 (2);
+ ifelse_val2 += test_ifelse2 (2);
+ ifelse_val2 += test_ifelse2 (2);
+ ifelse_val2 += test_ifelse2 (3);
+ ifelse_val2 += test_ifelse2 (3);
+
+ ifelse_val3 += test_ifelse3 (11, 19);
+ ifelse_val3 += test_ifelse3 (25, 27);
+ ifelse_val3 += test_ifelse3 (11, 22);
+ ifelse_val3 += test_ifelse3 (11, 10);
+ ifelse_val3 += test_ifelse3 (21, 32);
+ ifelse_val3 += test_ifelse3 (21, 20);
+ ifelse_val3 += test_ifelse3 (1, 2);
+ ifelse_val3 += test_ifelse3 (32, 31);
+ ifelse_val3 += test_ifelse3 (3, 0);
+ ifelse_val3 += test_ifelse3 (0, 47);
+ ifelse_val3 += test_ifelse3 (65, 65);
+
+ test_ifelse4 (1, 0);
+ test_ifelse5 (1, 0);
+ test_ifelse6 (1, 0);
+ test_ifelse7 (1, 0);
+ test_ifelse8 (1, 0);
+}
+
+int switch_val, switch_m;
+int
+test_switch (int i, int j)
+{
+ int result = 0; /* count(5) */
+ /* We can disable individual statements and breaks in the switch. */
+ switch (i) /* count(5) */
+ {
+ case 1:
+ __attribute__((disable_coverage))
+ result = do_something (2); /* count(#) */
+ break; /* count(1) */
+ case 2:
+ result = do_something (1024);
+ break;
+ case 3:
+ case 4:
+ if (j == 2) /* count(3) */
+ return do_something (4); /* count(1) */
+ result = do_something (8); /* count(2) */
+ __attribute__((disable_coverage))
+ break; /* count(#) */
+ default:
+ result = do_something (32); /* count(1) */
+ __attribute__((disable_coverage))
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ /* We can disable the whole switch. */
+ __attribute__((disable_coverage))
+ switch (i)
+ {
+ case 1:
+ result = do_something (64); /* count(#) */
+ break; /* count(#) */
+ case 2:
+ result = do_something (128); /* count(#) */
+ break; /* count(#) */
+ case 3:
+ result = do_something (256); /* count(#) */
+ break; /* count(#) */
+ default:
+ result = do_something (512); /* count(#) */
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ return result; /* count(4) */
+}
+
+void
+call_switch ()
+{
+ switch_val += test_switch (1, 0);
+ switch_val += test_switch (3, 0);
+ switch_val += test_switch (3, 2);
+ switch_val += test_switch (4, 0);
+ switch_val += test_switch (16, 0);
+ switch_val += switch_m;
+}
+
+/* The goto tests from gcov-4.c. */
+int goto_val;
+
+int
+test_goto1 (int f)
+{
+ __attribute__((disable_coverage))
+ if (f) /* count(#) */
+ goto lab1; /* count(#) */
+ return 1; /* count(1) */
+lab1:
+ return 2; /* count(1) */
+}
+
+int
+test_goto2 (int f)
+{
+ int i;
+ for (i = 0; i < 10; i++) /* count(15) */
+ if (i == f) goto lab2; /* count(14) */
+ return 4; /* count(1) */
+lab2:
+ // Add an empty statement so the attribute is applied to the return, not the
+ // label.
+ ;
+ __attribute__((disable_coverage))
+ return 8; /* count(#) */
+}
+
+int
+test_goto3 (int i, int j)
+{
+ if (j) /* count(1) */
+ __attribute__((disable_coverage))
+ goto else_; /* count(#) */
+
+top:
+ if (i) /* count(1) */
+ {
+ i = do_something (i);
+ }
+ else
+ {
+else_: /* count(1) */
+ j = do_something (j); /* count(2) */
+ __attribute__((disable_coverage))
+ if (j) /* count(#) */
+ {
+ j = 0; /* count(#) */
+ goto top; /* count(#) */
+ }
+ }
+ return 16; /* count(1) */
+}
+
+/* Not from gcov-4.c */
+int
+test_goto4 (int f, int g)
+{
+ /* The attribute should apply to all statements inside the {}, even the goto
+ when the label is inside the disabled block. When jumping out, the
+ destination should still be counted. */
+ __attribute__((disable_coverage))
+ {
+ if (f) /* count(#) */
+ goto inside; /* count(#) */
+ if (g) /* count(#) */
+ goto outside; /* count(#) */
+
+ inside:
+ if (g) /* count(#) */
+ goto skip; /* count(#) */
+ f += 2; /* count(#) */
+
+ skip:
+ g += 2; /* count(#) */
+ }
+ return 1; /* count(3) */
+outside:
+ return 2; /* count(1) */
+}
+
+/* Based on gcov-18.c */
+int
+test_goto5 (int a)
+{
+ /* If just one statement is ignored, the whole line should be. */
+ __attribute__((disable_coverage))
+ noop (); goto baz; lab: a = do_something (a+1); /* count(#) */
+ baz:
+ if (a == 1) /* count(2) */
+ goto lab; /* count(1) */
+ return a;
+}
+
+int
+test_goto6 (int a)
+{
+ __attribute__((disable_coverage))
+ {
+ a += 1; /* count(#) */
+ if (a >= 2) /* count(#) */
+ goto goto5_1; /* count(#) */
+
+ a += 10; /* count(#) */
+ if (a >= 20) /* count(#) */
+ goto goto5_2; /* count(#) */
+
+
+ goto5_1: /* count(#) */
+ a *= 3; /* count(#) */
+ goto goto5_2; /* count(#) */
+
+ goto5_2: /* count(#) */
+ a -= 2; /* count(#) */
+
+ goto5_3: /* count(#) */
+ a += 4; /* count(#) */
+ goto goto5_after; /* count(#) */
+ }
+ // __ENABLE call here
+ a *= 2; /* count(-) */
+
+ goto5_after: /* count(1) */
+ a -= 1; /* count(1) */
+ return a; /* count(1) */
+}
+
+void
+call_goto ()
+{
+ goto_val += test_goto1 (0);
+ goto_val += test_goto1 (1);
+ goto_val += test_goto2 (3);
+ goto_val += test_goto2 (30);
+ goto_val += test_goto3 (0, 1);
+
+ goto_val += test_goto4 (0, 0);
+ goto_val += test_goto4 (0, 1);
+ goto_val += test_goto4 (1, 0);
+ goto_val += test_goto4 (1, 1);
+
+ goto_val += test_goto5 (1);
+ goto_val += test_goto6 (1);
+}
+
+/* Returns, guarded by both plain values and function calls. */
+void
+return1 (int a, int b, int c)
+{
+ __attribute__((disable_coverage))
+ if (a) return; /* count(#) */
+ __attribute__((disable_coverage))
+ if (do_something (b)) return; /* count(#) */
+ if (do_something (c)) /* count(1) */
+ __attribute__((disable_coverage)) return; /* count(#) */
+}
+
+void
+call_return ()
+{
+ return1 (1, 0, 0);
+ return1 (0, 1, 0);
+ return1 (0, 0, 1);
+}
+
+/* From gcov-6.c */
+int test_exit_val;
+
+void
+test_exit1 (int i)
+{
+ /* An abnormal exit should not break suppression. */
+ __attribute__((disable_coverage))
+ if (i < 0) /* count(#) */
+ exit (0); /* count(#) */
+ test_exit_val += i; /* count(3) */
+}
+
+void
+test_exit2 (int i)
+{
+ if (i < 0) /* count(4) */
+ __attribute__((disable_coverage))
+ exit (0); /* count(#) */
+ test_exit_val += i; /* count(3) */
+}
+
+void
+test_exit3 (int i)
+{
+ /* There can be statements on either side of exit (). */
+ __attribute__((disable_coverage))
+ if (i < 0) /* count(#) */
+ {
+ test_exit_val += i; /* count(#) */
+ exit (0); /* count(#) */
+ test_exit_val += i; /* count(-) */
+ }
+ test_exit_val += i; /* count(3) */
+}
+
+void
+call_exit ()
+{
+ for (int i = 0; i != 3; ++i)
+ test_exit1 (i);
+ for (int i = 0; i != 3; ++i)
+ test_exit2 (i);
+ for (int i = 0; i != 3; ++i)
+ test_exit3 (i);
+
+ test_exit2 (-1);
+}
+
+int
+computed_goto1 (int a)
+{
+ /* __attribute__((disable_coverage))
+ void *op = &&dest;
+
+ is parsed as an attribute on the declaration void *op, not the assignment.
+ We can "fix" this by splitting the declaration and assignment. */
+ void *op;
+ __attribute__((disable_coverage))
+ op = &&dest; /* count(#) */
+dest:
+ if (op && a > 0) /* count(6) */
+ {
+ a -= 1; /* count(5) */
+ goto *op; /* count(5) */
+ }
+
+ return a;
+}
+
+int
+computed_goto2 (int a)
+{
+ void *op = &&dest; /* count(1) */
+dest:
+ ;
+ __attribute__((disable_coverage))
+ if (op && a > 0) /* count(#) */
+ {
+ a -= 1; /* count(#) */
+ goto *op; /* count(#) */
+ }
+
+ return a;
+}
+
+int
+computed_goto3 (int a)
+{
+ void *op = &&dest; /* count(1) */
+dest:
+ ;
+ if (op && a > 0) /* count(6) */
+ {
+ a -= 1; /* count(5) */
+ __attribute__((disable_coverage))
+ goto *op; /* count(#) */
+ }
+
+ return a;
+}
+
+void
+call_computed_goto ()
+{
+ computed_goto1 (5);
+ computed_goto2 (5);
+ computed_goto3 (5);
+}
+
+int main ()
+{
+ empty_body_for_loop ();
+ ignored_for_loop ();
+ declarations (1);
+ compound_statements (1);
+ call_while ();
+ call_for ();
+ call_ifelse ();
+ call_switch ();
+ call_goto ();
+ call_return ();
+ call_computed_goto ();
+
+ /* The final test will actually exit, so make sure to call it last. */
+ call_exit ();
+ return 0;
+}
+
+/* { dg-final { run-gcov { gcov-35.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-36.c
b/gcc/testsuite/gcc.misc-tests/gcov-36.c
new file mode 100644
index 00000000000..f4121ebd7dc
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-36.c
@@ -0,0 +1,98 @@
+/* { dg-options "--coverage -std=c23" } */
+/* { dg-do run { target native } } */
+
+/* This file contains a select few of the tests in gcov-35.c, but uses the C23
+ [[attribute]] syntax instead of __attribute__((attr)). */
+
+int do_something (int x) { return x; }
+
+int
+empty_body_for_loop ()
+{
+ int i;
+ [[gnu::disable_coverage]]
+ for (i = 0; i < 10; i++) /* count(#) */
+ ;
+ return i;
+}
+
+void
+return1 (int a, int b, int c)
+{
+ [[gnu::disable_coverage]]
+ if (a) return; /* count(#) */
+ [[gnu::disable_coverage]]
+ if (do_something (b)) return; /* count(#) */
+ if (do_something (c)) /* count(1) */
+ [[gnu::disable_coverage]]
+ return; /* count(#) */
+}
+
+int
+test_switch (int i, int j)
+{
+ int result = 0; /* count(5) */
+ int switch_m = 0;
+ /* We can disable individual statements and breaks in the switch. */
+ switch (i) /* count(5) */
+ {
+ case 1:
+ [[gnu::disable_coverage]]
+ result = do_something (2); /* count(#) */
+ break; /* count(1) */
+ case 2:
+ result = do_something (1024);
+ break;
+ case 3:
+ case 4:
+ if (j == 2) /* count(3) */
+ return do_something (4); /* count(1) */
+ result = do_something (8); /* count(2) */
+ [[gnu::disable_coverage]]
+ break; /* count(#) */
+ default:
+ result = do_something (32); /* count(1) */
+ [[gnu::disable_coverage]]
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ /* We can disable the whole switch. */
+ [[gnu::disable_coverage]]
+ switch (i)
+ {
+ case 1:
+ result = do_something (64); /* count(#) */
+ break; /* count(#) */
+ case 2:
+ result = do_something (128); /* count(#) */
+ break; /* count(#) */
+ case 3:
+ result = do_something (256); /* count(#) */
+ break; /* count(#) */
+ default:
+ result = do_something (512); /* count(#) */
+ switch_m++; /* count(#) */
+ break;
+ }
+
+ return result; /* count(4) */
+}
+
+int
+main ()
+{
+ empty_body_for_loop ();
+
+ return1 (1, 0, 0);
+ return1 (0, 1, 0);
+ return1 (0, 0, 1);
+
+ test_switch (1, 0);
+ test_switch (3, 0);
+ test_switch (3, 2);
+ test_switch (4, 0);
+ test_switch (16, 0);
+}
+
+/* { dg-final { run-gcov gcov-36.c } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-37.c
b/gcc/testsuite/gcc.misc-tests/gcov-37.c
new file mode 100644
index 00000000000..8f121f3f3b7
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-37.c
@@ -0,0 +1,171 @@
+/* { dg-options "--coverage -fpath-coverage" } */
+/* { dg-do run { target native } } */
+
+/* An obvious use case of [[gnu::disable_coverage]] is to support
+ contracts/pre- and post-conditions, without having the contradictions
+ messing up the coverage reports.
+
+ The tests are written in terms of prime path coverage, because it more
+ accurately detects when things are *not* included. Here's how gcov -b would
+ report the branches:
+
+ #: 13: REQUIRE(x >= 0); // branch(100)
+ branch 0 taken 100%
+ // branch(end)
+
+ The branch test would not detect if gcov had also printed the
+ should-be-ignored-branch:
+
+ branch 0 taken 100%
+ branch 1 taken 0%
+*/
+
+int identity (int x) { return x; }
+
+#define REQUIRE(pred) __attribute__((disable_coverage)) if (!(pred)) return -1
+#define ENSURE(pred) __attribute__((disable_coverage)) if (!(pred)) return -1
+
+/* BEGIN paths
+ summary: 1/1
+ expect covered: 35(ignore) 40(ignore) 42
+
+ There are really 5 prime paths through this function, but 4 of them should
+ be ignored. */
+int
+contracts1 (int x, int y)
+/* END */
+{
+ REQUIRE (x >= 0);
+ REQUIRE (y >= 0);
+ int z = x + y;
+ ENSURE (z >= x && z >= y);
+ return z;
+}
+
+/* BEGIN paths
+ summary: 0/1
+ expect: 52(ignore) 57(ignore) 59
+
+ We're failing a precondition, which should not contribute to coverage
+*/
+int
+contracts2 (int x, int y)
+/* END */
+{
+ REQUIRE (x >= 0);
+ REQUIRE (y >= 0);
+ int z = x + y;
+ ENSURE (z >= x && z >= y);
+ return z;
+}
+
+/* BEGIN paths
+ summary: 11/14
+
+ This is the reference function. It's body should be identical to
+ disable_in_loopN. All functions should be called with the same
+ arguments, but disable different parts of the function.
+ [[gnu::disable_coverage]] may change the graph (insert blocks), so the
+ number of paths may change slightly. */
+int
+disabled_in_loop (int len)
+/* END */
+{
+ int x = len;
+ x = identity (x);
+ x *= 5;
+ for (int i = 0; i < len; ++i)
+ {
+ x += identity (i);
+
+ if (i > 5)
+ x += 1;
+ }
+
+ return x;
+}
+
+/* BEGIN paths
+ summary: 9/12
+
+ We're definitely expecting not taking any path from the top into the THEN of
+ (i > 5).
+ expect: 100 102 102(ignore) 107(true) 108 102
+*/
+int
+disabled_in_loop1 (int len)
+/* END */
+{
+ int x = len;
+ x = identity (x);
+ x *= 5;
+ for (int i = 0; i < len; ++i)
+ {
+ __attribute__((disable_coverage))
+ x += identity (i);
+
+ if (i > 5)
+ x += 1;
+ }
+
+ return x;
+}
+
+/* BEGIN paths
+ summary: 6/7 */
+int
+disabled_in_loop2 (int len)
+/* END */
+{
+ int x = len;
+ x = identity (x);
+ x *= 5;
+ for (int i = 0; i < len; ++i)
+ {
+ x += identity (i);
+
+ __attribute__((disable_coverage))
+ if (i > 5)
+ x += 1;
+ }
+
+ return x;
+}
+
+/* BEGIN paths
+ summary: 0/1
+
+ By disabling the full loop we should only have a single path through the
+ function, as-if the loop isn't there. */
+int
+disabled_in_loop3 (int len)
+/* END */
+{
+ int x = len;
+ x = identity (x);
+ x *= 5;
+ __attribute__((disable_coverage))
+ for (int i = 0; i < len; ++i)
+ {
+ x += identity (i);
+
+ if (i > 5)
+ x += 1;
+ }
+
+ return x;
+}
+
+int
+main ()
+{
+ contracts1 (2, 4);
+ contracts2 (-2, 4);
+ contracts2 (2, -4);
+ disabled_in_loop (10);
+ disabled_in_loop1 (10);
+ disabled_in_loop2 (10);
+ disabled_in_loop3 (10);
+}
+
+/* { dg-final { run-gcov prime-paths { --prime-paths-lines=both gcov-37.c } }
} */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-38.c
b/gcc/testsuite/gcc.misc-tests/gcov-38.c
new file mode 100644
index 00000000000..a6a68ac70b9
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-38.c
@@ -0,0 +1,96 @@
+/* { dg-options "--coverage" } */
+/* { dg-do run { target native } } */
+/* { dg-require-effective-target indirect_jumps } */
+
+#include <setjmp.h>
+extern void abort (void);
+extern void exit (int);
+
+jmp_buf longjmp1_env;
+int longjmp1_val;
+int longjmp1_taken;
+int longjmp1_bar_enter, longjmp1_bar_exit;
+int longjmp1_foo_enter, longjmp1_foo_exit;
+
+/* Based on gcov-7.c */
+
+void
+longjmp1_bar (int i)
+{
+ longjmp1_bar_enter++; /* count(3) */
+ if (i == 0) {
+ __attribute__((disable_coverage))
+ longjmp1_taken++; /* count(#) */
+ longjmp (longjmp1_env, 1); /* count(1) */
+ }
+ longjmp1_val += i+1; /* count(2) */
+ longjmp1_bar_exit++; /* count(2) */
+}
+
+void
+longjmp1_foo (int i)
+{
+ longjmp1_foo_enter++; /* count(3) */
+ if (i == 1) {
+ longjmp1_taken++; /* count(1) */
+ __attribute__((disable_coverage))
+ longjmp (longjmp1_env, 2); /* count(#) */
+ }
+ longjmp1_bar (i); /* count(2) */
+ longjmp1_bar (7); /* count(1) */
+ longjmp1_val += 16;
+ longjmp1_foo_exit++; /* count(1) */
+}
+
+void
+longjmp1 ()
+{
+ int retlongjmp1_val;
+ __attribute__((disable_coverage))
+ if ((retlongjmp1_val = setjmp (longjmp1_env))) {
+ longjmp1_val += retlongjmp1_val; /* count(#) */
+ }
+ longjmp1_foo (longjmp1_val); /* count(3) */
+
+ if (!(longjmp1_val == 31 &&
+ longjmp1_taken == 2 &&
+ longjmp1_foo_enter == 3 &&
+ longjmp1_foo_exit == 1 &&
+ longjmp1_bar_enter == 3 &&
+ longjmp1_bar_exit == 2))
+ abort ();
+}
+
+/* Based on pr85372.c */
+void *buf[5];
+
+void fjmp (void) {
+ __builtin_longjmp (buf, 1);
+}
+
+int
+pr85372 (void)
+{
+ int last = 0;
+
+ if (__builtin_setjmp (buf) == 0) { /* count(2) */
+ __builtin_printf("True branch\n");
+ __attribute__((disable_coverage))
+ while (1) {
+ last = 1; /* count(#) */
+ fjmp (); /* count(#) */
+ }
+ } else {
+ __builtin_printf("False branch\n");
+ }
+
+ return 0;
+}
+
+int main ()
+{
+ longjmp1 ();
+ pr85372 ();
+}
+
+/* { dg-final { run-gcov { gcov-38.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-39.c
b/gcc/testsuite/gcc.misc-tests/gcov-39.c
new file mode 100644
index 00000000000..0bb1c3a8db6
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-39.c
@@ -0,0 +1,29 @@
+/* { dg-do run { target native } } */
+
+/* __attribute__((disable_coverage)) should be a no-op when coverage isn't
+ enabled. */
+
+int do_something (int x) {
+ return x;
+}
+
+int main (int argc, char **argv)
+{
+ __attribute__((disable_coverage))
+ int b = argc + 1;
+
+ __attribute__((disable_coverage))
+ int c;
+
+ int a = argc;
+
+ if (a)
+ if (b)
+ __attribute__((disable_coverage))
+ c = do_something (4);
+ else
+ c = do_something (1024);
+
+ __attribute__((disable_coverage))
+ int d = a + c - 1;
+}
diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
index f9a71f0a8be..6bd762bd83d 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -141,6 +141,9 @@ static void remove_bb (basic_block);
static edge find_taken_edge_computed_goto (basic_block, tree);
static edge find_taken_edge_cond_expr (const gcond *, tree);
+bool coverage_disabled_p (basic_block);
+void disable_coverage (basic_block);
+
void
init_empty_tree_cfg_for_function (struct function *fn)
{
@@ -493,6 +496,8 @@ make_blocks_1 (gimple_seq seq, basic_block bb)
bool start_new_block = true;
bool first_stmt_of_seq = true;
+ auto_vec<int> disable;
+
while (!gsi_end_p (i))
{
/* PREV_STMT should only be set to a debug stmt if the debug
@@ -510,6 +515,24 @@ make_blocks_1 (gimple_seq seq, basic_block bb)
if (stmt && is_gimple_call (stmt))
gimple_call_initialize_ctrl_altering (stmt);
+ if (stmt && gimple_call_internal_p (stmt, IFN_DISABLE_COVERAGE))
+ {
+ auto uid = tree_to_shwi (gimple_call_arg (stmt, 0));
+ disable.safe_push (uid);
+ gsi_remove (&i, true);
+ continue;
+ }
+
+ if (stmt && gimple_call_internal_p (stmt, IFN_ENABLE_COVERAGE))
+ {
+ auto uid = tree_to_shwi (gimple_call_arg (stmt, 0));
+ gcc_assert (!disable.is_empty ());
+ gcc_assert (uid == disable.last ());
+ disable.pop ();
+ gsi_remove (&i, true);
+ continue;
+ }
+
/* If the statement starts a new basic block or if we have determined
in a previous pass that we need to create a new block for STMT, do
so now. */
@@ -526,6 +549,17 @@ make_blocks_1 (gimple_seq seq, basic_block bb)
codes. */
gimple_set_bb (stmt, bb);
+ if (!disable.is_empty ())
+ disable_coverage (bb);
+
+ if (stmt && gimple_call_internal_p (stmt, IFN_ENABLE_COVERAGE))
+ {
+ auto uid = tree_to_shwi (gimple_call_arg (stmt, 0));
+ gcc_assert (!disable.is_empty ());
+ gcc_assert (uid == disable.last ());
+ disable.pop ();
+ }
+
/* If STMT is a basic block terminator, set START_NEW_BLOCK for the
next iteration. */
if (stmt_ends_bb_p (stmt))
@@ -622,6 +656,35 @@ make_blocks (gimple_seq seq)
}
}
+ if (gimple_call_internal_p (*gsi_start (seq), IFN_DISABLE_COVERAGE))
+ {
+ /* Insert a NOP before the 1st statement iff DISABLE_COVERAGE is the
+ first thing to happen in the function. This seems necessary for
+ lambdas of the form:
+ [](int x) { // coverage disabled
+ [[gnu::disable_coverage]]
+ if (x)
+ fn ();
+ ...
+ };
+
+
+ [](int x) { // coverage enabled
+ x += 1;
+ [[gnu::disable_coverage]]
+ if (x)
+ fn ();
+ ...
+ };
+ where the coverage of the lambda is tied to the first basic block.
+ Without this fix, the lambda count invocation count would be
+ ignored.
+ */
+
+ gimple *nop = gimple_build_nop ();
+ auto i = gsi_start (seq);
+ gsi_insert_seq_before (&i, nop, GSI_NEW_STMT);
+ }
make_blocks_1 (seq, ENTRY_BLOCK_PTR_FOR_FN (cfun));
}
@@ -1874,6 +1937,9 @@ gimple_can_merge_blocks_p (basic_block a, basic_block b)
if (stmt && stmt_ends_bb_p (stmt))
return false;
+ if (coverage_disabled_p (a) != coverage_disabled_p (b))
+ return false;
+
/* Examine the labels at the beginning of B. */
for (gimple_stmt_iterator gsi = gsi_start_bb (b); !gsi_end_p (gsi);
gsi_next (&gsi))
@@ -2756,6 +2822,17 @@ stmt_starts_bb_p (gimple *stmt, gimple *prev_stmt)
if (prev_stmt && is_gimple_debug (prev_stmt))
return false;
+ /* .ENABLE/.DISABLE always starts new blocks. Inserted by
+ [[gnu::disable_coverage]]. */
+ if (stmt && gimple_call_internal_p (stmt, IFN_DISABLE_COVERAGE))
+ return true;
+ if (stmt && gimple_call_internal_p (stmt, IFN_ENABLE_COVERAGE))
+ return true;
+ if (prev_stmt && gimple_call_internal_p (prev_stmt, IFN_DISABLE_COVERAGE))
+ return true;
+ if (prev_stmt && gimple_call_internal_p (prev_stmt, IFN_ENABLE_COVERAGE))
+ return true;
+
/* Labels start a new basic block only if the preceding statement
wasn't a label of the same type. This prevents the creation of
consecutive blocks that have nothing but a single label. */
@@ -2934,6 +3011,8 @@ gimple_split_edge (edge edge_in)
new_bb = create_empty_bb (after_bb);
new_bb->count = edge_in->count ();
+ if (coverage_disabled_p (after_bb))
+ disable_coverage (new_bb);
/* We want to avoid re-allocating PHIs when we first
add the fallthru edge from new_bb to dest but we also
@@ -6312,6 +6391,8 @@ gimple_split_block (basic_block bb, void *stmt)
edge_iterator ei;
new_bb = create_empty_bb (bb);
+ if (coverage_disabled_p (bb))
+ disable_coverage (new_bb);
/* Redirect the outgoing edges. */
new_bb->succs = bb->succs;
@@ -6468,6 +6549,8 @@ gimple_duplicate_bb (basic_block bb, copy_bb_data *id)
gimple_stmt_iterator gsi_tgt;
new_bb = create_empty_bb (EXIT_BLOCK_PTR_FOR_FN (cfun)->prev_bb);
+ if (coverage_disabled_p (bb))
+ disable_coverage (new_bb);
/* Copy the PHI nodes. We ignore PHI node arguments here because
the incoming edges have not been setup yet. */
@@ -9511,6 +9594,8 @@ insert_cond_bb (basic_block bb, gimple *stmt, gimple
*cond,
/* Create conditionally executed block. */
new_bb = create_empty_bb (bb);
+ if (coverage_disabled_p (bb))
+ disable_coverage (new_bb);
edge e = make_edge (bb, new_bb, EDGE_TRUE_VALUE);
e->probability = prob;
new_bb->count = e->count ();
--
2.47.3