[gcc r17-3215] Add attribute and pragma for coverage suppression
Jorgen Kvalsvik via Gcc-cvs <[email protected]>
| Newsgroups | gmane.comp.gcc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://gcc.gnu.org/g:f6a1e69999d7b614cbcedff6ecaa01b713f31266 commit r17-3215-gf6a1e69999d7b614cbcedff6ecaa01b713f31266 Author: Jørgen Kvalsvik <[email protected]> Date: Tue Sep 30 11:23:56 2025 +0200 Add attribute and pragma for coverage suppression Add support for suppressing coverage, either at the function level with the attribute [[gnu::suppress_coverage]] or at the line level with #pragma GCC suppress_coverage begin/end. This is quite useful for things like unreachable states which would score worse on coverage despite being better code, or when a specific functions should be omitted from coverage. A motivating example from the wild are 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, which pollutes the coverage report. GCC and gcov can already filter out on the function and file level (e.g. -fprofile-filter-files=<regex>), but that is not sufficient for this use case. The suppress_coverage attribute and pragma high precision tool for very specific bits of code that shouldn't be counted towards coverage. We can change the REQUIRE macro to this to make the problem go away: #define REQUIRE(pred) do { \ _Pragma ("GCC suppress_coverage begin"); \ assert (((void)"Precondition violated", pred)); \ _Pragma ("GCC suppress_coverage end"); \ } while (0) -- DEMO gcc div.c -o div --coverage && gcov -fbc div Without coverage suppression. 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:} The branch is effectively gone with suppresion: Function 'div' Lines executed:100.00% of 2 (1 of 3 suppressed) Branches executed:0.00% of 0 (2 of 2 suppressed) Taken at least once:0.00% of 0 Calls executed:0.00% of 0 (1 of 1 suppressed) -: 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) do { \ -: 4: _Pragma ("GCC suppress_coverage begin"); \ -: 5: assert (((void)msg, pred)); \ -: 6: _Pragma ("GCC suppress_coverage end"); \ -: 7:} while (0) -: 8: function div called 1 returned 100% blocks executed 80% 1: 9:double div (double x, double y) -: 10:{ -: 11: /* gcov reports that this function has no branches. */ #: 12: REQUIRE (y != 0, "division by zero"); 1: 13: return x / y; -: 14:} -: 15: function main called 1 returned 100% blocks executed 100% 1: 16:int main() { 1: 17: return div(0, 5); call 0 returned 1 -: 18:} -- IMPLEMENTATION DETAILS We record the expanded source location of the pragma and consult them during tree-cfg construction. If a statement's location is anchored between a begin/end, we record the basic block suppressed in the new SUPPRESS record. We split a block when only parts of it have coverage suppressed, and take suppression into account when we consider merging blocks, generally prohibiting merges with different suppression flags. This feature is designed so that it does not actually disable the arc profiling instrumentation itself as we don't want this attribute messing with profile-guided optimization or other uses. Instead, we do the actual suppression in gcov. However, when the whole function is within a begin/end, or when annotated with [[gnu::suppress_coverage]], we can disable MC/DC and prime path coverage as those are only used for coverage, not pgo. This just works; we just omit the record, and gcov is none the wiser. -- GCOV The coverage instrumentation is not actually changed in any meaningful way, suppressing coverage is about interpretation. Rather than changing the instrumentation, we add a new SUPPRESS 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 SUPPRESS 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 suppress out a subsequence we want to unify the paths that used to differ but now don't care about the path through the suppressed subgraph, and either may have been covered. Function-level suppressions are implemented by GCC recording the ENTRY block (0) as suppressed. This block cannot be marked by user programs anyway so we get to claim it, and it makes sense that an ignored entry block implies the whole function. -- gcc/c-family/ChangeLog: * c-attribs.cc (handle_suppress_coverage_attribute): New function. * c-pragma.cc (handle_pragma_suppress_coverage): New function. (init_pragma): Register pragma GCC suppress_coverage. * c-pragma.h (suppress_coverage_begin): New forward declaration. (suppress_coverage_end): Ditto. gcc/ChangeLog: * cfg-flags.def (COVERAGE_SUPPRESSED): New flag. * doc/extend.texi: Add sections on suppress_coverage. * doc/gcov.texi: Add sections on suppress_coverage. * gcov-dump.cc (tag_suppress): New function. * gcov-io.h (GCOV_TAG_SUPPRESS): New. (GCOV_TAG_SUPPRESS_LENGTH): New. (GCOV_TAG_SUPPRESS_NUM): New. * gcov.cc (struct arc_info): Add field suppressed. (block_info::block_info): Initialize suppressed. (line_info::line_info): Initialize suppressed. (struct coverage_info): Add fields lines_suppressed, (struct path_info): Add fields residual_paths, residual_covered. (executed_summary): Add suppressed argument. (tombstone_p): New function. (remove_tombstones): New function. (tombstone_subsequence_p): New function. (subsumed_by_any_p): New function. (path_info::suppress_blocks): New function. (path_info::get_paths): New function. (path_info::count): New function. (path_info::suppressed_count): New function. (path_info::suppressed_p): New function. (find_arc): Return NULL if arc cannot be found. (main): Pass total_suppressed to executed_summary. (output_intermediate_json_line): Skip suppressed. (json_set_prime_path_coverage): Include suppressed counts. (output_json_intermediate_file): Likewise. (process_all_functions): Record block, arc suppressed status. (generate_results): Count total suppressed. (read_graph_file): Read SUPPRESS tag. (add_branch_counts): Count suppressed. (add_condition_counts): Likewise. (add_path_counts): Likewise. (function_summary): Print info on suppressed blocks. (file_summary): Count suppressed. (add_line_counts): Likewise. (accumulate_line_info): Likewise. (accumulate_line_counts): Likewise. (output_conditions): Skip suppressed. (output_branch_count): Likewise. (print_prime_path_lines): Annotate suppressed arcs. (print_prime_path_source): Likewise, and widen the annotation column. (output_path_coverage): Print info on suppressed paths. (output_line_beginning): Prefix suppressed lines with '#'. (output_line_details): Pass suppressed. (output_lines): Likewise. * profile.cc (suppress_coverage): New function. (suppress_coverage_unset): New function. (coverage_suppressed_p): New function. (any_block_coverage_suppressed_p): New function. (suppress_coverage_begin): New function. (suppress_coverage_end): New function. (in_pragma_suppress_coverage_p): New function. (location_in_pragma_suppress_coverage_p): New function. (fn_in_pragma_suppress_coverage_p): New function. (branch_prob): Write SUPPRESS record, skip MC/DC and path coverage when the entire function is suppressed. * profile.h (in_pragma_suppress_coverage_p): New forward declaration. (location_in_pragma_suppress_coverage_p): Ditto. (coverage_suppressd_p): Ditto. (suppress_coverage): Ditto. (suppress_coverage_unset): Ditto. * tree-cfg.cc (gimple_empty_block_p): New forward declaration. (make_blocks_1): Set coverage-suppresesed on blocks. (make_blocks): Sometimes insert NOP to help coverage suppression on blocks. (gimple_can_merge_blocks_p): Don't merge coverage-suppressed block with non-suppressed blocks. (gimple_merge_blocks): Likewise. (gimple_split_edge): Propagate coverage-suppressed 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.dg/pragma-suppress-coverage.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.misc-tests/gcov-40.c: New test. * gcc.misc-tests/gcov-41.c: New test. * gcc.misc-tests/gcov-42.c: New test. * gcc.misc-tests/gcov-42.h: New test. Diff: --- gcc/c-family/c-attribs.cc | 21 + gcc/c-family/c-pragma.cc | 39 + gcc/c-family/c-pragma.h | 4 + gcc/cfg-flags.def | 3 + gcc/doc/extend.texi | 98 +++ gcc/doc/gcov.texi | 82 +- gcc/gcov-dump.cc | 22 + gcc/gcov-io.h | 3 + gcc/gcov.cc | 532 ++++++++++-- gcc/profile.cc | 157 +++- gcc/profile.h | 7 + gcc/testsuite/g++.dg/gcov/gcov-24.C | 1022 ++++++++++++++++++++++ gcc/testsuite/g++.dg/gcov/gcov-25.C | 23 + gcc/testsuite/g++.dg/gcov/gcov-26.C | 548 ++++++++++++ gcc/testsuite/g++.dg/gcov/gcov-27.C | 80 ++ gcc/testsuite/gcc.dg/pragma-suppress-coverage.c | 32 + gcc/testsuite/gcc.misc-tests/gcov-37.c | 1061 +++++++++++++++++++++++ gcc/testsuite/gcc.misc-tests/gcov-38.c | 23 + gcc/testsuite/gcc.misc-tests/gcov-39.c | 243 ++++++ gcc/testsuite/gcc.misc-tests/gcov-40.c | 100 +++ gcc/testsuite/gcc.misc-tests/gcov-41.c | 35 + gcc/testsuite/gcc.misc-tests/gcov-42.c | 43 + gcc/testsuite/gcc.misc-tests/gcov-42.h | 4 + gcc/tree-cfg.cc | 82 ++ 24 files changed, 4194 insertions(+), 70 deletions(-) diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index aecfdcda4906..b3d5407ec0b3 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -194,6 +194,9 @@ static tree handle_null_terminated_string_arg_attribute (tree *, tree, tree, int static tree handle_btf_decl_tag_attribute (tree *, tree, tree, int, bool *); static tree handle_btf_type_tag_attribute (tree *, tree, tree, int, bool *); +static tree handle_suppress_coverage_attribute (tree *, tree, tree, int, + bool *); + /* Helper to define attribute exclusions. */ #define ATTR_EXCL(name, function, type, variable) \ { name, function, type, variable } @@ -644,6 +647,8 @@ const struct attribute_spec c_common_gnu_attributes[] = handle_special_var_sec_attribute, attr_section_exclusions }, { "access", 1, 3, false, true, true, false, handle_access_attribute, NULL }, + { "suppress_coverage", 0, 0, true, false, false, false, + handle_suppress_coverage_attribute, NULL }, /* Attributes used by Objective-C. */ { "NSObject", 0, 0, true, false, false, false, handle_nsobject_attribute, NULL }, @@ -5490,6 +5495,22 @@ handle_nonstring_attribute (tree *node, tree name, tree ARG_UNUSED (args), return NULL_TREE; } +/* Handle the "suppress_coverge" attribute. We don't really do anything here, + just check later if it is set. */ +static tree +handle_suppress_coverage_attribute (tree *node, tree name, + tree ARG_UNUSED (args), + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + } + + return NULL_TREE; +} + /* Given a function type FUNCTYPE, returns the type of the parameter ARGNO or null if ARGNO exceeds the number of parameters. On failure set *NARGS to the number of function parameters. */ diff --git a/gcc/c-family/c-pragma.cc b/gcc/c-family/c-pragma.cc index 4903dcaa0823..d0d4ff69fd7c 100644 --- a/gcc/c-family/c-pragma.cc +++ b/gcc/c-family/c-pragma.cc @@ -1041,6 +1041,43 @@ handle_pragma_diagnostic_early_pp (cpp_reader *) handle_pragma_diagnostic_impl<true, true> (); } +/* Parse #pragma GCC suppress_coverage begin|end to stop lines from contributing + towards (missing) coverage. */ +static void +handle_pragma_suppress_coverage (cpp_reader*) +{ + tree x; + location_t loc; + auto token = pragma_lex (&x); + enum { bad, begin, end } action = bad; + + if (token == CPP_NAME) + { + const char *op = IDENTIFIER_POINTER (x); + if (!strcmp (op, "begin")) + action = begin; + else if (!strcmp (op, "end")) + action = end; + } + + if (bad == action) + GCC_BAD ("%<#pragma GCC suppress_coverage%> must be followed by %<begin%> " + "or %<end%>"); + else if (end == action) + { + if (!suppress_coverage_end (input_location)) + GCC_BAD ("no matching begin for %<#pragma GCC suppress_coverage end%>"); + } + else + { + if (!suppress_coverage_begin (input_location)) + GCC_BAD ("%<#pragma GCC suppress_coverage begin%> " + "was already in effect, ignored"); + } + if (pragma_lex (&x, &loc) != CPP_EOF) + GCC_BAD_AT (loc, "junk at end of %<#pragma GCC suppress_coverage%>"); +} + /* Parse #pragma GCC target (xxx) to set target specific options. */ static void handle_pragma_target(cpp_reader *) @@ -1837,6 +1874,8 @@ init_pragma (void) c_register_pragma (0, "weak", handle_pragma_weak); c_register_pragma ("GCC", "visibility", handle_pragma_visibility); + c_register_pragma ("GCC", "suppress_coverage", + handle_pragma_suppress_coverage); if (flag_preprocess_only) c_register_pragma_with_early_handler ("GCC", "diagnostic", diff --git a/gcc/c-family/c-pragma.h b/gcc/c-family/c-pragma.h index ff299f4e1a78..5d28cb45c669 100644 --- a/gcc/c-family/c-pragma.h +++ b/gcc/c-family/c-pragma.h @@ -303,4 +303,8 @@ extern void c_pp_lookup_pragma (unsigned int, const char **, const char **); extern GTY(()) tree pragma_extern_prefix; +/* For recording #pragma GCC suppress_coverage locations. */ +extern bool suppress_coverage_begin (location_t); +extern bool suppress_coverage_end (location_t); + #endif /* GCC_C_PRAGMA_H */ diff --git a/gcc/cfg-flags.def b/gcc/cfg-flags.def index cbb4bb970272..0b75cf328b4b 100644 --- a/gcc/cfg-flags.def +++ b/gcc/cfg-flags.def @@ -93,6 +93,9 @@ DEF_BASIC_BLOCK_FLAG(VISITED, 13) demand, and is available after calling compute_transaction_bits(). */ DEF_BASIC_BLOCK_FLAG(IN_TRANSACTION, 14) +/* Set on blocks that are in #pragma GCC suppress_coverage blocks. */ +DEF_BASIC_BLOCK_FLAG(COVERAGE_SUPPRESSED, 15) + #endif #ifdef DEF_EDGE_FLAG diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 225f75e7217e..7116ebb96e1b 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -2815,6 +2815,19 @@ positional initialization will result in future breakage. GCC emits warnings based on this attribute by default; use @option{-Wno-designated-init} to suppress them. +@cindex @code{suppress_coverage} +@item suppress_coverage +This attribute applies to functions. + +The @code{suppress_coverage} attribute results in GCC not considering the +function or its lines, arcs, conditions, or paths towards the total +count. This attribute does nothing unless coverage is enabled, +e.g. with @option{--coverage} or @option{-fpath-coverage}. This is useful +to suppress coverage for functions that are uninteresting or impractical to +cover. If coverage is suppressed with this attribute then GCC will not +instrument for MC/DC @option{-fcondition-coverage} or prime path +coverage @option{-fpath-coverage}. + @atindex @code{error} @atindex @code{warning} @cindex functions that are supposed to be optimized away @@ -10138,6 +10151,7 @@ option is specified. @xref{OpenMP}, and @ref{OpenACC}. * Structure-Layout Pragmas:: * Weak Pragmas:: * Diagnostic Pragmas:: +* Coverage Pragmas:: * Visibility Pragmas:: * Push/Pop Macro Pragmas:: * Function Specific Option Pragmas:: @@ -10602,6 +10616,90 @@ an error as well. @end table +@node Coverage Pragmas +@subsection Coverage Pragmas + +@table @code +@cindex pragma, suppress_coverage +@item #pragma GCC suppress_coverage begin +@itemx #pragma GCC suppress_coverage end +The lines of code between the @code{begin} and @code{end} will not be +considered towards any coverage. This is independent of the code +structure, and @code{end} may appear in a different scope than the +corresponding @code{begin}. + +This pragma 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}. + +A @code{end} without a @code{begin}, or a @code{begin} when another is +already in effect, will generate a warning. A @code{begin} without a +matching @code{end} will suppress the rest of the file. + +@smallexample + #pragma GCC suppress_coverage begin + if (cond) + @{ + /* Coverage is disbled for all statements in this block. */ + foo (); + @} + else + @{ + #pragma GCC suppress_coverage end + /* Coverage is restored. */ + bar (); + @} + + /* Counts towards coverage. */ + int a = 42; + +#pragma GCC suppress_coverage begin + @{ + /* Does not count towards coverage. */ + bar (&a); + ... + @} +@end smallexample + +Asserts introduce a branch, and the goal of asserting invariants is that +the assert should not trigger, which would leave the assertion-failure +branch uncovered. These branches should not be taken (or cannot, in +some cases), and would make full coverage impossible, yet clearly +contribute to correctness. By suppressing coverage for asserts we can +fix this problem. + +@smallexample +#define REQUIRE(pred, msg) do @{ \ + _Pragma ("GCC suppress_coverage begin") \ + assert (((void)msg, pred)); \ + _Pragma ("GCC suppress_coverage end") \ + @} while (0) + +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 + +@samp{pragma GCC suppress_coverage} can suppress expressions like the +condition in @code{if} statements: + +@smallexample +#pragma GCC suppress_coverage begin +if (x < y) // Does not count towards coverage. +#pragma GCC suppress_coverage end + @{ + /* Counts towards coverage. */ + foo (); + @} +@end smallexample + +@end table + @node Visibility Pragmas @subsection Visibility Pragmas diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi index a5fed102026a..64ce9e3a152c 100644 --- a/gcc/doc/gcov.texi +++ b/gcc/doc/gcov.texi @@ -228,7 +228,8 @@ path 4 not covered: lines 8 8(false) 11(true) 11 13(false) 16 17 This means to cover path 2 you must run lines 8, 11, 13, 14, and 17, evaluating the decision at 8 false and the decisions at 11 and 13 to -@code{false}. +@code{false}. @code{<line>(suppress)} means that @code{<line>} is the +last line before a sequence of suppressed statements. @item --prime-paths-source [=@var{type}] Write path coverage to the output file, and write path summary info to @@ -671,7 +672,8 @@ containing no code. Unexecuted lines are marked @samp{#####} or non-exceptional paths or only exceptional paths such as C++ exception handlers, respectively. Given the @samp{-a} option, unexecuted blocks are marked @samp{$$$$$} or @samp{%%%%%}, depending on whether a basic block -is reachable via non-exceptional or exceptional paths. +is reachable via non-exceptional or exceptional paths. Suppressed lines +are marked with @samp{#}. Executed basic blocks having a statement with zero @var{execution_count} end with @samp{*} character and are colored with magenta color with the @option{-k} option. This functionality is not supported in Ada. @@ -1177,6 +1179,82 @@ decisions like @option{--prime-paths-source} but printed on a single line. This mode provides a good overview over the paths and for tracking how different tests and inputs exercises the code. +Coverage can be suppressed by using +@code{#pragma GCC suppress_coverage}. The code within @code{begin} and +@code{end} will not contribute to coverage totals. This is very useful +for unreachable or hard-to-reach code, precondition asserts/contracts, +or other code that is noise in the coverage report. Suppressed lines +are annotated with @samp{#} in the @var{execution_count} column. + +@smallexample +$ gcov -t tmp + -: 0:Source:tmp.c + -: 0:Graph:tmp.gcno + -: 0:Data:tmp.gcda + -: 0:Runs:1 + -: 1:#include <stdio.h> + -: 2:#include <stdlib.h> + -: 3: + -: 4:int + 1: 5:main (void) + -: 6:@{ + -: 7: int i, total; + 1: 8: total = 0; + -: 9: + 11: 10: for (i = 0; i < 10; i++) + 10: 11: total += i; + -: 12: + -: 13:#pragma GCC suppress_coverage begin + #: 14: if (i < 0) + #: 15: abort (); + -: 16:#pragma GCC suppress_coverage end + -: 17: + 1*: 18: int v = total > 100 ? 1 : 2; + -: 19: + 1*: 20: if (total != 45 && v == 1) + #####: 21: printf ("Failure\n"); + -: 22: else + 1: 23: printf ("Success\n"); + 1: 24: return 0; + -: 25:@} +@end smallexample + +New prime paths are computed when code is suppressed by merging the +suppressed code into a superblocks, and considering any path through the +superblock as coverage. This is an example where a full if-then-else is +suppressed. Path 4 is @code{total > 100} is followed by any path +through the @samp{(suppressed)} segment to the @code{return 0}. + +@smallexample +$ gcov -t --prime-paths-source tmp +path 4 not covered: +BB 3: 10: for (i = 0; i < 10; i++) +BB 3: 11: total += i; +BB 4: (false) 10: for (i = 0; i < 10; i++) +BB 5: (true) 13: int v = total > 100 ? 1 : 2; +BB 6: (suppress) 13: int v = total > 100 ? 1 : 2; +BB 12: 21: return 0; + + 1: 5:main (void) + -: 6:@{ + -: 7: int i, total; + 1: 8: total = 0; + -: 9: + 11: 10: for (i = 0; i < 10; i++) + 10: 11: total += i; + -: 12: + 1*: 13: int v = total > 100 ? 1 : 2; + -: 14: + -: 15:#pragma GCC suppress_coverage begin + #: 16: if (total != 45 && v == 1) + #: 17: printf ("Failure\n"); + -: 18: else + #: 19: printf ("Success\n"); + -: 20:#pragma GCC suppress_coverage end + 1: 21: return 0; + -: 22:@} +@end smallexample + The execution counts are cumulative. If the example program were executed again without removing the @file{.gcda} file, the count for the number of times each line in the source was executed would be added to diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc index 0f9992c60f1b..709b0449ab6c 100644 --- a/gcc/gcov-dump.cc +++ b/gcc/gcov-dump.cc @@ -41,6 +41,7 @@ static void tag_arcs (const char *, unsigned, int, unsigned); static void tag_conditions (const char *, unsigned, int, unsigned); static void tag_paths (const char *, unsigned, int, unsigned); static void tag_lines (const char *, unsigned, int, unsigned); +static void tag_suppress (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 **); @@ -82,6 +83,7 @@ static const tag_format_t tag_table[] = {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions}, {GCOV_TAG_PATHS, "PATHS", tag_paths}, {GCOV_TAG_LINES, "LINES", tag_lines}, + {GCOV_TAG_SUPPRESS, "SUPPRESS", tag_suppress}, {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary}, {0, NULL, NULL} }; @@ -473,6 +475,26 @@ tag_lines (const char *filename ATTRIBUTE_UNUSED, } } +static void +tag_suppress (const char *filename ATTRIBUTE_UNUSED, + unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED, + unsigned depth) +{ + if (flag_dump_contents) + { + unsigned nblocks = GCOV_TAG_SUPPRESS_NUM (length); + printf (" %u blocks suppressed", 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 5a0b109c49b4..d6ac2651778a 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_SUPPRESS ((gcov_unsigned_t)0x01510000) +#define GCOV_TAG_SUPPRESS_LENGTH(NUM) ((NUM) * GCOV_WORD_SIZE) +#define GCOV_TAG_SUPPRESS_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 9797ddd8ca8d..87deeadcc756 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 suppressed arc by #pragma GCC suppress_coverage. */ + unsigned int suppressed : 1; + /* Links to next arc on src and dst lists. */ struct arc_info *succ_next; struct arc_info *pred_next; @@ -146,15 +149,42 @@ public: path is covered. */ vector<gcov_type_unsigned> covered; + /* The prime paths after #pragma GCC suppress_coverage has been taken into + account. This is empty unless something is suppressed, in which case it + should be smaller than PATHS. The paths are lexicographically sorted. */ + vector<vector<unsigned>> residual_paths; + + /* The covered paths after #pragma GCC suppress_coverage has been taken into + account. Like with COVERED, the bit N is set if the Nth path in + RESIDUAL_PATHS is covered. */ + vector<gcov_type_unsigned> residual_covered; + /* 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 !suppressed_p () ? paths : residual_paths; } + + /* Get the number of paths, accounting for suppressed blocks. */ + size_t path_count () const + { return get_paths ().size (); } + + /* Get the number of suppressed paths. This is 0 unless there is a #pragma + GCC suppress_coverage somewhere. */ + size_t suppressed_count () const + { return paths.size () - path_count (); } + + /* Check if any paths suppressed by #pragma GCC suppress_coverage. */ + bool suppressed_p () const + { return !residual_paths.empty (); } + + /* Count the covered paths, accounting for #pragma GCC suppress_coverage. */ unsigned covered_paths () const { unsigned cnt = 0; - for (gcov_type_unsigned v : covered) + for (gcov_type_unsigned v : (!suppressed_p () ? covered : residual_covered)) cnt += popcount_hwi (v); return cnt; } @@ -164,10 +194,14 @@ public: { if (covered.empty ()) return false; + + const auto& cov = !suppressed_p () ? covered : residual_covered; 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 suppress_blocks (const vector<bool>& suppressed); }; /* Describes which locations (lines and files) are associated with @@ -244,6 +278,9 @@ public: /* Block is a landing pad for longjmp or throw. */ unsigned is_nonlocal_return : 1; + /* Block is suppressed by #pragma GCC suppress_coverage. */ + unsigned suppressed : 1; + condition_info conditions; vector<block_location_info> locations; @@ -266,7 +303,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) + suppressed (0), locations (), chain (NULL) { cycle.arc = NULL; } @@ -295,10 +332,12 @@ public: unsigned exists : 1; unsigned unexceptional : 1; unsigned has_unexecuted_block : 1; + /* Suppressed by #pragma GCC suppress_coverage. */ + unsigned suppressed : 1; }; line_info::line_info (): count (0), branches (), blocks (), exists (false), - unexceptional (0), has_unexecuted_block (0) + unexceptional (0), has_unexecuted_block (0), suppressed (0) { } @@ -385,6 +424,11 @@ public: /* Next function. */ class function_info *next; + /* Blocks suppressed by #pragma GCC suppress_coverage. If any block is + suppressed this is non-empty, and the Nth bit is true if N is suppressed. + If suppressed_blocks[0] is true, the whole function is suppressed. */ + vector<bool> suppressed_blocks; + /* Get demangled name of a function. The demangled name is converted when it is used for the first time. */ char *get_demangled_name () @@ -410,6 +454,11 @@ public: { return blocks.size () - 2; } + + bool suppressed_p () const + { + return !suppressed_blocks.empty () && suppressed_blocks.front (); + } }; /* Function info comparer that will sort functions according to starting @@ -430,23 +479,30 @@ struct function_line_start_cmp struct coverage_info { + int function_suppressed; + int lines; int lines_executed; + int lines_suppressed; int branches; int branches_executed; int branches_taken; + int branches_suppressed; int conditions; int conditions_covered; + int conditions_suppressed; int calls; int calls_executed; + int calls_suppressed; char *name; unsigned paths; unsigned paths_covered; + unsigned paths_suppressed; }; /* Describes a file mentioned in the block graph. Contains an array @@ -608,6 +664,7 @@ static unsigned object_runs; static unsigned total_lines; static unsigned total_executed; +static unsigned total_suppressed; /* Modification time of graph file. */ @@ -814,7 +871,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 +918,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. Suppressed 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. Suppressed + 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 SUPPRESSED. + Does nothing when SUPPRESSED is empty. This only adds the new + interpretation and does not change the observed path and coverage info. */ +void +path_info::suppress_blocks (const vector<bool>& suppressed) +{ + if (suppressed.empty ()) + return; + + const unsigned tombstone = unsigned (-1); + /* Clean up the paths by replacing suppressed 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 (!suppressed[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++; + } + residual_covered.swap (nextcovered); + + /* Store the new paths. The map iteration outputs the paths + lexicographically ordered. */ + for (auto& p : nextpaths) + residual_paths.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 +1342,7 @@ main (int argc, char **argv) } if (!flag_use_stdout) - executed_summary (total_lines, total_executed); + executed_summary (total_lines, total_executed, total_suppressed); return return_code; } @@ -1384,7 +1647,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)->suppressed) + { + /* 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 +1679,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)->suppressed) + continue; + const condition_info& info = (*it)->conditions; if (info.n_terms == 0) continue; @@ -1518,12 +1788,14 @@ 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 ("suppressed_prime_paths", + info.paths.suppressed_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 +1816,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.true_value) + const arc_info *arc = find_arc (block, path[i+1]); + if (!arc) + edge_kind = "suppress"; + else if (arc->true_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 +1881,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_suppressed", + count ((*it)->suppressed_blocks.begin (), + (*it)->suppressed_blocks.end (), true)); function->set_integer ("execution_count", (*it)->blocks[0].count); json_set_prime_path_coverage (*function, **it); @@ -1770,6 +2047,19 @@ process_all_functions (void) function_info *fn = *it; unsigned src = fn->src; + if (!fn->suppressed_blocks.empty ()) + { + /* Set the ignore flag on blocks, arcs. */ + for (block_info &b : fn->blocks) + if (fn->suppressed_blocks[b.id] || fn->suppressed_p ()) + { + b.suppressed = 1; + for (arc_info *arc = b.succ; arc; arc = arc->succ_next) + arc->suppressed = 1; + for (arc_info *arc = b.pred; arc; arc = arc->pred_next) + arc->suppressed = 1; + } + } if (!fn->counts.empty () || no_data_file) { source_info *s = &sources[src]; @@ -1807,6 +2097,10 @@ process_all_functions (void) } } } + + if (block->suppressed || fn->suppressed_p ()) + for (unsigned ln : block->locations[i].lines) + s->lines[ln].suppressed = 1; } } @@ -1821,12 +2115,22 @@ process_all_functions (void) if (fn->is_group) fn->lines.resize (fn->end_line - fn->start_line + 1); + /* Propagate the suppressed flag too. */ + if (fn->is_group) + { + const auto& source = sources[fn->src]; + for (unsigned ln = fn->start_line, dst = 0; ln <= fn->end_line; + ++ln, ++dst) + fn->lines[dst].suppressed = source.lines.at (ln).suppressed; + } + solve_flow_graph (fn); if (fn->has_catch) find_exception_blocks (fn); /* For path coverage. */ find_prime_paths (fn); + fn->paths.suppress_blocks (fn->suppressed_blocks); } else { @@ -1883,6 +2187,8 @@ generate_results (const char *file_name) coverage_info coverage; memset (&coverage, 0, sizeof (coverage)); + if (fn->suppressed_p ()) + coverage.function_suppressed = 1; coverage.name = fn->get_name (); add_line_counts (flag_function_summary ? &coverage : NULL, fn); @@ -1953,6 +2259,7 @@ generate_results (const char *file_name) file_summary (&src->coverage); total_lines += src->coverage.lines; total_executed += src->coverage.lines_executed; + total_suppressed += src->coverage.lines_suppressed; if (flag_gcov_file) { if (flag_json_format) @@ -2337,6 +2644,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->suppressed = 0; arc->succ_next = src_blk->succ; src_blk->succ = arc; @@ -2383,6 +2691,21 @@ read_graph_file (void) } } } + else if (fn && tag == GCOV_TAG_SUPPRESS) + { + const unsigned nblocks = GCOV_TAG_SUPPRESS_NUM (length); + if (!fn->suppressed_blocks.empty ()) + fnotice (stderr, "%s:already seen suppressed blocks for '%s'\n", + bbg_file_name, fn->get_name ()); + fn->suppressed_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->suppressed_blocks[idx] = true; + } + } else if (fn && tag == GCOV_TAG_CONDS) { unsigned num_dests = GCOV_TAG_CONDS_NUM (length); @@ -2960,16 +3283,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->suppressed) + coverage->calls_suppressed++; + else if (arc->src->count) coverage->calls_executed++; } else if (!arc->is_unconditional) { coverage->branches++; - if (arc->src->count) + if (arc->src->count && !arc->suppressed) coverage->branches_executed++; - if (arc->count) + if (arc->count && !arc->suppressed) coverage->branches_taken++; + if (arc->suppressed) + coverage->branches_suppressed++; } } @@ -2979,7 +3306,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->suppressed) + coverage->conditions_suppressed += 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 +3318,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_suppressed += fn.paths.suppressed_count (); } /* Format COUNT, if flag_human_readable_numbers is set, return it human @@ -3048,11 +3379,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 suppressed) { - if (lines) + if (lines && suppressed == 0) fnotice (stdout, "Lines executed:%s of %d\n", format_gcov (executed, lines, 2), lines); + else if (lines && suppressed > 0) + fnotice (stdout, "Lines executed:%s of %d (%d of %d suppressed)\n", + format_gcov (executed, lines - suppressed, 2), lines - suppressed, + suppressed, lines); else fnotice (stdout, "No executable lines\n"); } @@ -3062,45 +3397,77 @@ executed_summary (unsigned lines, unsigned executed) static void function_summary (const coverage_info *coverage) { + if (coverage->function_suppressed) + { + fnotice (stdout, "Function '%s' suppressed\n", coverage->name); + return; + } fnotice (stdout, "%s '%s'\n", "Function", coverage->name); - executed_summary (coverage->lines, coverage->lines_executed); + executed_summary (coverage->lines, coverage->lines_executed, + coverage->lines_suppressed); 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_suppressed; + if (coverage->branches_suppressed == 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 suppressed)\n", + format_gcov (coverage->branches_executed, branches, 2), + branches, coverage->branches_suppressed, 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_suppressed > 0) + fnotice (stdout, "Calls executed:%s of %d (%d of %d suppressed)\n", + format_gcov (coverage->calls_executed, coverage->calls + - coverage->calls_suppressed, 2), + coverage->calls - coverage->calls_suppressed, + coverage->calls_suppressed, coverage->calls); else fnotice (stdout, "No calls\n"); if (flag_conditions) { - if (coverage->conditions) + if (coverage->conditions && coverage->conditions_suppressed == 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_suppressed > 0) + fnotice (stdout, "Condition outcomes covered:%s of %d" + " (%d of %d suppressed)\n", + format_gcov (coverage->conditions_covered, + coverage->conditions + - coverage->conditions_suppressed, 2), + coverage->conditions - coverage->conditions_suppressed, + coverage->conditions_suppressed, coverage->conditions); else fnotice (stdout, "No conditions\n"); } if (flag_prime_paths) { - if (coverage->paths) + if (coverage->paths && coverage->paths_suppressed == 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_suppressed > 0) + fnotice (stdout, "Prime paths covered:%s of %d (%u of %u suppressed)\n", + format_gcov (coverage->paths_covered, coverage->paths + - coverage->paths_suppressed, 2), + coverage->paths - coverage->paths_suppressed, + coverage->paths_suppressed, coverage->paths); else fnotice (stdout, "No path information\n"); } @@ -3112,7 +3479,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_suppressed); if (flag_branches) { @@ -3322,7 +3690,9 @@ add_line_counts (coverage_info *coverage, function_info *fn) { if (!line->exists) coverage->lines++; - if (!line->count && block->count) + if (line->suppressed) + coverage->lines_suppressed++; + if (!line->count && block->count && !line->suppressed) coverage->lines_executed++; } line->exists = 1; @@ -3342,7 +3712,9 @@ add_line_counts (coverage_info *coverage, function_info *fn) { if (!line->exists) coverage->lines++; - if (!line->count && block->count) + if (!line->exists && line->suppressed) + coverage->lines_suppressed++; + if (!line->count && block->count && !line->suppressed) coverage->lines_executed++; } line->exists = 1; @@ -3429,7 +3801,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->suppressed) + src->coverage.lines_suppressed++; + if (line->count && !line->suppressed) src->coverage.lines_executed++; } } @@ -3482,7 +3856,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->suppressed) + src->coverage.lines_suppressed++; + if (!src_line->count && fn_line->count && !src_line->suppressed) src->coverage.lines_executed++; src_line->count += fn_line->count; @@ -3508,6 +3884,8 @@ output_conditions (FILE *gcov_file, const block_info *binfo) const condition_info& info = binfo->conditions; if (info.n_terms == 0) return; + if (binfo->suppressed) + return; const int expected = 2 * info.n_terms; const int got = info.popcount (); @@ -3535,7 +3913,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->suppressed) + return 0; + else if (arc->is_call_non_return) { if (arc->src->count) { @@ -3610,13 +3990,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 = "(suppress)"; + 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 +4061,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 = "(suppress)"; + 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)"; } @@ -3702,12 +4085,12 @@ print_prime_path_source (FILE *gcov_file, const function_info &fn, for (unsigned i = 0; i != loc.lines.size () - 1; ++i) { const unsigned line = loc.lines[i]; - fprintf (gcov_file, "BB %2d: %-7s %3d", bb, "", line); + fprintf (gcov_file, "BB %2d: %-10s %3d", bb, "", line); print_source_line (gcov_file, lines, line); } const unsigned line = loc.lines.back (); - fprintf (gcov_file, "BB %2d: %-7s %3d", bb, edge_kind, line); + fprintf (gcov_file, "BB %2d: %-10s %3d", bb, edge_kind, line); print_source_line (gcov_file, lines, line); } } @@ -3725,8 +4108,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.suppressed_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 + " suppressed)\n", fn->paths.covered_paths (), + (fmt_size_t)fn->paths.path_count (), + (fmt_size_t)fn->paths.suppressed_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 +4125,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 +4227,19 @@ pad_count_string (string &s) static void output_line_beginning (FILE *f, bool exists, bool unexceptional, bool has_unexecuted_block, + bool suppressed, gcov_type count, unsigned line_num, const char *exceptional_string, const char *unexceptional_string, unsigned int maximum_count) { string s; - if (exists) + if (suppressed) + { + s = "#"; + pad_count_string (s); + } + else if (exists) { if (count > 0) { @@ -3935,6 +4332,7 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num) { output_line_beginning (f, line->exists, (*it)->exceptional, false, + (*it)->suppressed, (*it)->count, line_num, "%%%%%", "$$$$$", 0); fprintf (f, "-block %d", (*it)->id); @@ -4092,14 +4490,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 suppressed, 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->suppressed, + line->count, line_num, "=====", "#####", src->maximum_count); @@ -4138,13 +4537,14 @@ 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. */ + the source line. For suppressed lines, 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->suppressed, line->count, l, "=====", "#####", src->maximum_count); diff --git a/gcc/profile.cc b/gcc/profile.cc index 17841fc723a0..5ae64d4043fd 100644 --- a/gcc/profile.cc +++ b/gcc/profile.cc @@ -66,6 +66,8 @@ along with GCC; see the file COPYING3. If not see #include "cfgloop.h" #include "sreal.h" #include "file-prefix-map.h" +#include "stringpool.h" +#include "attribs.h" #include "profile.h" #include "auto-profile.h" @@ -1205,6 +1207,134 @@ read_thunk_profile (struct cgraph_node *node) return; } +/* Disable coverage for BB. This is used for #pragma GCC suppress_coverage. */ +void +suppress_coverage (basic_block bb) +{ + bb->flags |= BB_COVERAGE_SUPPRESSED; +} + +/* Unset the flag set by suppress_coverage. This is only useful when merging + blocks. */ +void +suppress_coverage_unset (basic_block bb) +{ + bb->flags &= ~BB_COVERAGE_SUPPRESSED; +} + +/* Check if BB has coverage disabled by #pragma GCC suppress_coverage. */ +bool +coverage_suppressed_p (basic_block bb) +{ + return bb->flags & BB_COVERAGE_SUPPRESSED; +} + +/* Check if any blocks are disabled by #pragma suppress_coverage in the current + function. */ +static bool +any_block_coverage_suppressed_p () +{ + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + if (coverage_suppressed_p (bb)) + return true; + return false; +} + +/* The source locations of #pragma GCC suppress_coverage begin/end. For each + entry, the source_range m_finish/m_end should be the (expanded) source + location of the begin/end. If there is no end, m_finish will be + UNKNOWN_LOCATION. */ +static vec<source_range> suppress_coverage_ranges; + +/* Try to add LOC as the beginning of a new range. If a range was started + already, this is a no-op. Returns true if a new range was created. */ +bool +suppress_coverage_begin (location_t loc) +{ + if (!suppress_coverage_ranges.is_empty () + && suppress_coverage_ranges.last ().m_finish == UNKNOWN_LOCATION) + return false; + + loc = get_pure_location (expansion_point_location (loc)); + source_range range = source_range::from_locations (loc, UNKNOWN_LOCATION); + suppress_coverage_ranges.safe_push (range); + return true; +} + +/* Try to close the last range created by suppress_coverage_begin at LOC. If + the range has been closed already (or not opened), this is a no-op. Returns + true if a range was closed. */ +bool +suppress_coverage_end (location_t loc) +{ + if (suppress_coverage_ranges.is_empty () + || suppress_coverage_ranges.last ().m_finish != UNKNOWN_LOCATION) + return false; + loc = get_pure_location (expansion_point_location (loc)); + suppress_coverage_ranges.last ().m_finish = loc; + return true; +} + +/* Check if STMT is anchored to a line of code in a range disabled by #pragma + GCC suppress_coverage begin/end. This function always returns false if + coverage is disabled as it is the faster check, and nothing should be + suppressed anyway. + + If STMT is at an UNKNOWN_LOCATION or ADHOC_LOC, this function returns PREV. + This is probably a compiler-generated statement that should inherit the + disabled state of the previous statement since it is really tied to it, and + there is no opportunity for a #pragma in-between. */ +bool +in_pragma_suppress_coverage_p (gimple* stmt, bool prev) +{ + if (!coverage_instrumentation_p ()) + return false; + + location_t loc = expansion_point_location (gimple_location (stmt)); + if (loc == UNKNOWN_LOCATION || IS_ADHOC_LOC (loc)) + return prev; + + return location_in_pragma_suppress_coverage_p (loc); +} + +/* Check if LOC is within a #pragma GCC suppress_coverage block. */ +bool +location_in_pragma_suppress_coverage_p (location_t loc) +{ + loc = get_pure_location (expansion_point_location (loc)); + for (const source_range& dl : suppress_coverage_ranges) + if (linemap_location_before_p (line_table, dl.m_start, loc) + && (linemap_location_before_p (line_table, loc, dl.m_finish) + || dl.m_finish == UNKNOWN_LOCATION)) + return true; + return false; +} + +/* Check if FN is fully between #pragma GCC suppress_coverage begin/end. In + that case we can disable the whole function rather than every block, and + omit MC/DC (-fcondition-coverage) and prime path coverage (-fpath-coverage) + instrumentation. */ +static bool +fn_in_pragma_suppress_coverage_p (function *fn) +{ + if (!coverage_instrumentation_p ()) + return false; + + if (lookup_attribute ("gnu", "suppress_coverage", + DECL_ATTRIBUTES (fn->decl))) + return true; + + const location_t start = fn->function_start_locus; + const location_t end = fn->function_end_locus; + + for (const source_range& dl : suppress_coverage_ranges) + if (linemap_location_before_p (line_table, dl.m_start, start) + && (linemap_location_before_p (line_table, end, dl.m_finish) + || dl.m_finish == UNKNOWN_LOCATION)) + return true; + return false; +} /* Instrument and/or analyze program behavior based on program the CFG. @@ -1478,6 +1608,8 @@ branch_prob (bool thunk) lineno_checksum = coverage_compute_lineno_checksum (); } + const bool fn_coverage_suppressed_p = fn_in_pragma_suppress_coverage_p (cfun); + /* Write the data from which gcov can reconstruct the basic block graph and function line numbers (the gcno file). */ output_to_file = false; @@ -1538,6 +1670,27 @@ branch_prob (bool thunk) gcov_write_length (offset); } + /* Disabled blocks or function. Lines, arcs, path segments through + ignored blocks should not count towards coverage. Ignoring coverage + is a matter of interpretation and does not change the instrumentation; + gcov sorts it out. If the whole function is disabled (by the + attribute on the function, not the statements), the entry block is + recorded as ignored. */ + if (fn_coverage_suppressed_p) + { + offset = gcov_write_tag (GCOV_TAG_SUPPRESS); + gcov_write_unsigned (ENTRY_BLOCK); + gcov_write_length (offset); + } + else if (any_block_coverage_suppressed_p ()) + { + offset = gcov_write_tag (GCOV_TAG_SUPPRESS); + FOR_EACH_BB_FN (bb, cfun) + if (coverage_suppressed_p (bb)) + gcov_write_unsigned (bb->index); + gcov_write_length (offset); + } + /* Line numbers. */ /* Initialize the output. */ output_location (&streamed_locations, NULL, 0, NULL, NULL); @@ -1612,7 +1765,7 @@ branch_prob (bool thunk) if (condition_coverage_flag || path_coverage_flag || profile_arc_flag) gimple_init_gcov_profiler (); - if (condition_coverage_flag) + if (condition_coverage_flag && !fn_coverage_suppressed_p) { struct condcov *cov = find_conditions (cfun); gcc_assert (cov); @@ -1662,7 +1815,7 @@ branch_prob (bool thunk) } unsigned instrument_prime_paths (struct function*); - if (path_coverage_flag) + if (path_coverage_flag && !fn_coverage_suppressed_p) { const unsigned npaths = instrument_prime_paths (cfun); if (output_to_file) diff --git a/gcc/profile.h b/gcc/profile.h index 3c57a40c2c71..04f506af78aa 100644 --- a/gcc/profile.h +++ b/gcc/profile.h @@ -81,4 +81,11 @@ extern struct gcov_summary *profile_info, *gcov_profile_info; -fcondition-coverage -fpath-coverage. */ extern bool coverage_instrumentation_p (); +/* For #pragma GCC suppress_coverage begin/end. */ +extern bool in_pragma_suppress_coverage_p (gimple*, bool); +extern bool location_in_pragma_suppress_coverage_p (location_t); +extern bool coverage_suppressed_p (basic_block); +extern void suppress_coverage (basic_block); +extern void suppress_coverage_unset (basic_block); + #endif /* PROFILE_H */ 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 000000000000..d68fda7dbc18 --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-24.C @@ -0,0 +1,1022 @@ +/* { dg-options "--coverage" } */ +/* { dg-do run } */ + +/* testsuite/gcc.misc-tests/gcov-37.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; +#pragma GCC suppress_coverage begin + for (i = 0; i < 10; i++) /* count(#) */ +#pragma GCC suppress_coverage end + ; + return i; +} + +/* Making the for (;;) multi line should report count per line. g++ considers + the i++ unexecuted, while gcc counts it. */ +int +ignored_for_loop () +{ + int i; +#pragma GCC suppress_coverage begin + for (i = 0; i < 10; i++) /* count(#) */ + { + noop (); /* count(#) */ + noop (); /* count(#) */ + } +#pragma GCC suppress_coverage end + + /* Making the for (;;) multi line should report count per line. */ +#pragma GCC suppress_coverage begin + for (i = 0; /* count(#) */ + i < 20; /* count(#) */ + i++) /* count(-) */ + { + noop (); /* count(#) */ + } +#pragma GCC suppress_coverage end + + noop (); + i++; + + return 0; /* count(1) */ +} + +int +declarations (int a) +{ + // Should work when it is the first declaration +#pragma GCC suppress_coverage begin + int b = a + 1; /* count(#) */ +#pragma GCC suppress_coverage end + + // 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. +#pragma GCC suppress_coverage begin + int c; +#pragma GCC suppress_coverage end + + a *= 2; /* count(1) */ + + // Should work when it is not the first declaration +#pragma GCC suppress_coverage begin + int d = a - 1; /* count(#) */ +#pragma GCC suppress_coverage end + + c = a+b+d; /* count(1) */ + return c; +} + +int +compound_statements (int a) +{ + int c; +#pragma GCC suppress_coverage begin + { + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + } +#pragma GCC suppress_coverage end + return c; /* count(1) */ +} + +int +while1 (int a) +{ +#pragma GCC suppress_coverage begin + while (a > 0) /* count(#) */ + a = do_something (a - 1); /* count(#) */ +#pragma GCC suppress_coverage end + return a; +} + +/* If the pragma stops blocks from merging this would end up with the wrong + count. The while (cond) should run 6 times (a = 5), but earlier drafts + clocked it at 7 because an empty block at the end of the body would have + coverage suppressed, while the loop header did which prevented merging. */ +int +while2 (int a) +{ + while (a > 0) /* count(6) */ +#pragma GCC suppress_coverage begin + a = do_something (a - 1); /* count(#) */ +#pragma GCC suppress_coverage end + return a; +} + +int +dowhile1 (int a) +{ +#pragma GCC suppress_coverage begin + do + { + a = do_something (a - 1); /* count(#) */ + } while (a > 0); /* count(#) */ +#pragma GCC suppress_coverage end + return a; +} + +int +dowhile2 (int a) +{ + do + { +#pragma GCC suppress_coverage begin + a = do_something (a - 1); /* count(#) */ +#pragma GCC suppress_coverage end + } 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; +#pragma GCC suppress_coverage begin + for (;; a++) /* count(#) */ + { + int c[1]; + if (a) /* count(#) */ + { + break; /* count(#) */ + a; /* count(-) */ + continue; /* count(#) */ + } + continue; /* count(#) */ + } +#pragma GCC suppress_coverage end + + a = 0; + for (;; a++) /* count(1) */ + { + int c[1]; +#pragma GCC suppress_coverage begin + if (a) /* count(#) */ + { + break; /* count(#) */ + a; /* count(-) */ + continue; /* count(#) */ + } +#pragma GCC suppress_coverage end + continue; /* count(1) */ + } + + a = 0; + for (;; a++) /* count(1) */ + { + int c[1]; + if (a) /* count(2) */ + { +#pragma GCC suppress_coverage begin + break; /* count(#) */ +#pragma GCC suppress_coverage end + 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(-) */ +#pragma GCC suppress_coverage begin + continue; /* count(#) */ +#pragma GCC suppress_coverage end + } + continue; /* count(1) */ + } + + a = 0; + for (;; a++) /* count(1) */ + { + int c[1]; + if (a) /* count(2) */ + { + break; /* count(1) */ + a; /* count(-) */ + continue; /* count(1) */ + } +#pragma GCC suppress_coverage begin + continue; /* count(#) */ +#pragma GCC suppress_coverage end + } + + 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) */ +#pragma GCC suppress_coverage begin + if (acc > 10) /* count(#) */ + break; /* count(#) */ +#pragma GCC suppress_coverage end + 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 break is a not-executable-line in C, but will be successfully be + ignored in C++. */ +#pragma GCC suppress_coverage begin + break; /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + for (k = 0; k < o; k++) /* count(#) */ + for_temp++; /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + result = do_something (4); /* count(#) */ + else +#pragma GCC suppress_coverage end + result = do_something (1024); + /* We can ignore the ELSE. */ + else + if (j) /* count(2) */ + result = do_something (1); /* count(1) */ + else +#pragma GCC suppress_coverage begin + result = do_something (2); /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + if (j > 10) /* count(#) */ + result = do_something (result*4); /* count(#) */ +#pragma GCC suppress_coverage end + return result; /* count(5) */ +} + +int +test_ifelse2 (int i) +{ + int result = 0; +#pragma GCC suppress_coverage begin + if (!i) /* count(#) */ + result = do_something (1); /* count(#) */ +#pragma GCC suppress_coverage end + + if (i == 1) /* count(6) */ + result = do_something (1024); + + if (i == 2) /* count(6) */ +#pragma GCC suppress_coverage begin + result = do_something (2); /* count(#) */ +#pragma GCC suppress_coverage end + + if (i == 3) /* count(6) */ +#pragma GCC suppress_coverage begin + return do_something (8); /* count(#) */ +#pragma GCC suppress_coverage end + +#pragma GCC suppress_coverage begin + if (i == 4) /* count(#) */ + return do_something (2048); /* count(#) */ +#pragma GCC suppress_coverage end + + return result; /* count(4) */ +} + +int +test_ifelse3 (int i, int j) +{ + int result = 1; + /* Multi-condition ifs are suppressed, too */ +#pragma GCC suppress_coverage begin + if (i > 10 && j > i && j < 20) /* count(#) */ + result = do_something (16); /* count(#) */ +#pragma GCC suppress_coverage end + + if (i == 3 || j == 47 || i == j) /* count(11) */ +#pragma GCC suppress_coverage begin + result = do_something (64); /* count(#) */ +#pragma GCC suppress_coverage end + + return result; /* count(11) */ +} + +/* These are based on gcov-17.c */ +int +test_ifelse4 (int true_var, int false_var) +{ + unsigned int ret = 0; +#pragma GCC suppress_coverage begin + if (true_var) /* count(#) */ + { + if (false_var) /* count(#) */ + ret = 111; /* count(#) */ + } + else + ret = 999; /* count(#) */ +#pragma GCC suppress_coverage end + return ret; +} + +int +test_ifelse5 (int true_var, int false_var) +{ + unsigned int ret = 0; + if (true_var) /* count(1) */ +#pragma GCC suppress_coverage begin + { + if (false_var) /* count(#) */ + ret = 111; /* count(#) */ +#pragma GCC suppress_coverage end + } + else + ret = 999; /* count(#####) */ + return ret; +} + +int +test_ifelse6 (int true_var, int false_var) +{ + unsigned int ret = 0; + if (true_var) /* count(1) */ + { +#pragma GCC suppress_coverage begin + if (false_var) /* count(#) */ + ret = 111; /* count(#) */ +#pragma GCC suppress_coverage end + } + 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) */ +#pragma GCC suppress_coverage begin + ret = 111; /* count(#) */ +#pragma GCC suppress_coverage end + } + 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 +#pragma GCC suppress_coverage begin + ret = 999; /* count(#) */ +#pragma GCC suppress_coverage end + return ret; +} + +int +test_ifelse9 (int true_var, int false_var) +{ + unsigned int ret = 0; + /* With the pragma we can disable condition, but still observe the then-else. */ +#pragma GCC suppress_coverage begin + if (true_var) /* count(#) */ +#pragma GCC suppress_coverage end + { + if (false_var) /* count(1) */ + ret = 111; /* count(#####) */ + } + else + 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); + test_ifelse9 (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: +#pragma GCC suppress_coverage begin + result = do_something (2); /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + break; /* count(#) */ +#pragma GCC suppress_coverage end + default: + result = do_something (32); /* count(1) */ +#pragma GCC suppress_coverage begin + switch_m++; /* count(#) */ +#pragma GCC suppress_coverage end + break; + } + + /* We can disable the whole switch. */ +#pragma GCC suppress_coverage begin + 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; + } +#pragma GCC suppress_coverage end + + return result; /* count(4) */ +} + +int +test_switch2 (int i, int j) +{ + int result = 0; /* count(1) */ + switch (i) /* count(1) */ + { + case 1: + result = do_something (2); /* count(#####) */ + break; /* count(#####) */ + case 2: + result = do_something (1024); + break; + case 3: + case 4: + if (j == 2) /* count(#####) */ + return do_something (4); /* count(#####) */ + result = do_something (8); /* count(#####) */ + break; /* count(#####) */ + /* We can disable the label itself with the pragma. */ +#pragma GCC suppress_coverage begin + default: /* count(#) */ + result = do_something (32); /* count(#) */ + switch_m++; /* count(#) */ + break; +#pragma GCC suppress_coverage end + } + + /* We can disable multiple cases with a single pragma. */ +#pragma GCC suppress_coverage begin + switch (i) /* count(#) */ + { + case 1: /* count(#) */ + result = do_something (64); /* count(#) */ + break; /* count(#) */ + case 2: + result = do_something (128); /* count(#) */ + break; /* count(#) */ +#pragma GCC suppress_coverage end + case 3: + result = do_something (256); /* count(#####) */ + break; /* count(#####) */ + default: + result = do_something (512); /* count(1) */ + switch_m++; /* count(1) */ + break; + } + + return result; /* count(1) */ +} + +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; + switch_val += test_switch2 (16, 0); +} + +/* The goto tests from gcov-4.c. */ +int goto_val; + +int +test_goto1 (int f) +{ +#pragma GCC suppress_coverage begin + if (f) /* count(#) */ + goto lab1; /* count(#) */ +#pragma GCC suppress_coverage end + 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. + ; +#pragma GCC suppress_coverage begin + return 8; /* count(#) */ +#pragma GCC suppress_coverage end +} + +int +test_goto3 (int i, int j) +{ + if (j) /* count(1) */ +#pragma GCC suppress_coverage begin + goto else_; /* count(#) */ +#pragma GCC suppress_coverage end + +top: + if (i) /* count(1) */ + { + i = do_something (i); + } + else + { +else_: /* count(1) */ + j = do_something (j); /* count(2) */ +#pragma GCC suppress_coverage begin + if (j) /* count(#) */ + { + j = 0; /* count(#) */ + goto top; /* count(#) */ + } +#pragma GCC suppress_coverage end + } + 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 suppressed block. When jumping out, the + destination should still be counted. */ +#pragma GCC suppress_coverage begin + { + 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(#) */ + } +#pragma GCC suppress_coverage end + 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. */ +#pragma GCC suppress_coverage begin + noop (); goto baz; lab: a = do_something (a+1); /* count(#) */ +#pragma GCC suppress_coverage end + baz: + if (a == 1) /* count(2) */ + goto lab; /* count(1) */ + return a; +} + +int +test_goto6 (int a) +{ +#pragma GCC suppress_coverage begin + { + 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(#) */ + } +#pragma GCC suppress_coverage end + 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. + + The report is slightly different between C and C++ and the #/- are anchored + to the right line. */ +void +return1 (int a, int b, int c) +{ +#pragma GCC suppress_coverage begin + if (a) return; /* count(#) */ +#pragma GCC suppress_coverage end +#pragma GCC suppress_coverage begin + if (do_something (b)) return; /* count(#) */ +#pragma GCC suppress_coverage end + if (do_something (c)) /* count(1) */ +#pragma GCC suppress_coverage begin /* count(-) */ + return; /* count(#) */ +#pragma GCC suppress_coverage end + if (do_something (c)) { /* count(#####) */ +#pragma GCC suppress_coverage begin /* count(-) */ + return; /* count(#) */ +#pragma GCC suppress_coverage end + } +} + +int +return2 (int a, int b, int c) +{ +#pragma GCC suppress_coverage begin + if (a) return a; /* count(#) */ +#pragma GCC suppress_coverage end /* count(-) */ +#pragma GCC suppress_coverage begin /* count(-) */ + if (do_something (b)) return b; /* count(#) */ +#pragma GCC suppress_coverage end /* count(-) */ + if (do_something (c)) /* count(1) */ +#pragma GCC suppress_coverage begin /* count(-) */ + return c; /* count(#) */ +#pragma GCC suppress_coverage end /* count(-) */ + return 0; +} + +void +call_return () +{ + return1 (1, 0, 0); + return1 (0, 1, 0); + return1 (0, 0, 1); + return2 (1, 0, 0); + return2 (0, 1, 0); + return2 (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. */ +#pragma GCC suppress_coverage begin + if (i < 0) /* count(#) */ + exit (0); /* count(#) */ +#pragma GCC suppress_coverage end + test_exit_val += i; /* count(3) */ +} + +void +test_exit2 (int i) +{ + if (i < 0) /* count(4) */ +#pragma GCC suppress_coverage begin + exit (0); /* count(#) */ +#pragma GCC suppress_coverage end + test_exit_val += i; /* count(3) */ +} + +void +test_exit3 (int i) +{ + /* There can be statements on either side of exit (). */ +#pragma GCC suppress_coverage begin + if (i < 0) /* count(#) */ + { + test_exit_val += i; /* count(#) */ + exit (0); /* count(#) */ + test_exit_val += i; /* count(-) */ + } +#pragma GCC suppress_coverage end + 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) +{ + void *op; +#pragma GCC suppress_coverage begin + op = &&dest; /* count(#) */ +#pragma GCC suppress_coverage end +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: + ; +#pragma GCC suppress_coverage begin + if (op && a > 0) /* count(#) */ + { + a -= 1; /* count(#) */ + goto *op; /* count(#) */ + } +#pragma GCC suppress_coverage end + + return a; +} + +int +computed_goto3 (int a) +{ + void *op = &&dest; /* count(1) */ +dest: + ; + if (op && a > 0) /* count(6) */ + { + a -= 1; /* count(5) */ +#pragma GCC suppress_coverage begin + goto *op; /* count(#) */ +#pragma GCC suppress_coverage end + } + + return a; +} + +void +call_computed_goto () +{ + computed_goto1 (5); + computed_goto2 (5); + computed_goto3 (5); +} + +__attribute__((suppress_coverage)) int +suppressed_function (int a) +{ + int c; + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + return c; /* count(#) */ +} + +#pragma GCC suppress_coverage begin +int +pragma_begin_outside (int a) +{ + int c; + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + return c; /* count(#) */ +} +#pragma GCC suppress_coverage end + +#pragma GCC suppress_coverage begin +int +pragma_end_middle (int a) +{ + int c; + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ +#pragma GCC suppress_coverage end + int d = a - 1; /* count(1) */ + c = a+b+d; /* count(1) */ + return c; /* count(1) */ +} + +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 (); + suppressed_function (1); + pragma_begin_outside (1); + pragma_end_middle (1); + + /* 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 000000000000..cebf27d5e596 --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-25.C @@ -0,0 +1,23 @@ +/* { dg-options "--coverage" } */ +/* { dg-do run } */ + +/* [[attribute]] syntax instead of __attribute__((attr)). */ + +[[gnu::suppress_coverage]] int +suppressed_function (int a) +{ + int c; + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + return c; /* count(#) */ +} + +int +main () +{ + suppressed_function (1); +} + +/* { 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 000000000000..60be9a38c18a --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-26.C @@ -0,0 +1,548 @@ +/* A collection of C++ specific constructs with coverage disabled. */ + +/* { dg-options "--coverage" } */ +/* { dg-do run } */ + +#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 + { +#pragma GCC suppress_coverage begin + throws (0); /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + throws (0); /* count(#) */ +#pragma GCC suppress_coverage end + } + 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 (...) + { +#pragma GCC suppress_coverage begin + noop (); /* count (#) */ +#pragma GCC suppress_coverage end + } +} + +/* We can disable try-catch altogether. */ +void +try_catch4 () +{ +#pragma GCC suppress_coverage begin + try + { + throws (0); /* count(#) */ + throws (0); /* count(#) */ + } + catch (...) + { + noop (); /* count(#) */ + } +#pragma GCC suppress_coverage end + + 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(=====) */ + } + +#pragma GCC suppress_coverage begin + try + { + throws (1); /* count(#) */ + throws (0); /* count(#) */ + } + catch (...) + { + noop (); /* count (#) */ + } +#pragma GCC suppress_coverage end +} + +void +try_catch6 () +{ + try + { + throws (0); /* count(1) */ + throws (0); /* count(1) */ + } + catch (...) + { + noop (); /* count(=====) */ + } + + try + { + throws (1); /* count(1) */ + throws (0); /* count(#####) */ + } + catch (...) + { +#pragma GCC suppress_coverage begin + { + noop (); /* count (#) */ + noop (); /* count (#) */ + } +#pragma GCC suppress_coverage end + } +} + +void +try_catch7 () +{ +#pragma GCC suppress_coverage begin + try + { + throws (0); /* count(#) */ + throws (0); /* count(#) */ + } + catch (...) + { + noop (); /* count(#) */ + } +#pragma GCC suppress_coverage end +} + +void +try_catch8 () +{ +#pragma GCC suppress_coverage begin + 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(#) */ + } +#pragma GCC suppress_coverage end + + try + { + throws_stdexcept (1); /* count(1) */ + throws_stdexcept (0); /* count(#####) */ + } + catch (std::length_error&) + { +#pragma GCC suppress_coverage begin + noop (4); /* count(#) */ +#pragma GCC suppress_coverage end + } + 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&) + { +#pragma GCC suppress_coverage begin + noop (8); /* count(#) */ +#pragma GCC suppress_coverage end + } + catch (std::runtime_error&) + { + noop (9); /* count(=====) */ + } +} + +/* We can start/stop coverage in different try/catch blocks, even across + different expressions. */ +void +try_catch9 () +{ + try + { + throws (0); /* count(1) */ +#pragma GCC suppress_coverage begin + throws (0); /* count(#) */ + } + catch (...) + { + noop (); /* count(#) */ + } + + try + { + throws (1); /* count(#) */ + throws (0); /* count(#) */ + } + catch (...) + { + noop (); /* count(#) */ +#pragma GCC suppress_coverage end + noop (); /* count(1) */ + } +} + +/* Throws are disabled, either directly or through its surrounding block. */ +int +ifelse_throw1 (int f) +{ +#pragma GCC suppress_coverage begin + if (f >= 2) /* count(#) */ + throw 1; /* count(#) */ +#pragma GCC suppress_coverage end + + return f; /* count(1) */ +} + +int +ifelse_throw2 (int f) +{ + if (f >= 2) /* count(2) */ +#pragma GCC suppress_coverage begin + throw 1; /* count(#) */ +#pragma GCC suppress_coverage end + + return f; /* count(1) */ +} + +int +ifelse_throw3 (int f) +{ +#pragma GCC suppress_coverage begin + if (f >= 2) /* count(#) */ + throw 1; /* count(#) */ +#pragma GCC suppress_coverage end + + return f; /* count(1) */ +} + +int +ifelse_throw4 (int f) +{ + if (f >= 2) /* count(2) */ +#pragma GCC suppress_coverage begin + throw 1; /* count(#) */ +#pragma GCC suppress_coverage end + + return f; /* count(1) */ +} + +int ctor_x; +/* Disabling coverage for a default constructor/initialization. */ +void +ctor1 () +{ + class C + { + int v; + public: + C() : v(5) {} + }; + +#pragma GCC suppress_coverage begin + C c; /* count(#) */ +#pragma GCC suppress_coverage end + // 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; + }; + +#pragma GCC suppress_coverage begin + C c (a); /* count(#) */ +#pragma GCC suppress_coverage end + // arbitrary action between ctor+dtor + ctor_x = 1; /* count(1) */ +} + +void +ctor3 () +{ + class C + { + int v; + public: + C() : v(5) {} + }; + +#pragma GCC suppress_coverage begin + C c; /* count(#) */ +#pragma GCC suppress_coverage end + ctor_x = 1; /* count(1) */ +} + +/* Disabling coverage for a constructor/initialization with args. */ +void +ctor4 (int a) +{ + class C + { + public: + explicit C (int e) : v (e) {} + int v; + }; + +#pragma GCC suppress_coverage begin + C c (a); /* count(#) */ +#pragma GCC suppress_coverage end + ctor_x = 1; /* count(1) */ +} + + +template <typename T> +T +incr1 (T v) +{ +#pragma GCC suppress_coverage begin + v += 1; /* count(#) */ +#pragma GCC suppress_coverage end + return v; +} + +template <typename T> +T +incr2 (T v) +{ +#pragma GCC suppress_coverage begin + v += 2; /* count(#) */ +#pragma GCC suppress_coverage end + return v; +} + +#pragma GCC suppress_coverage begin +template <typename T> +T +incr3 (T v) +{ + v += 3; /* count(#) */ +#pragma GCC suppress_coverage end + return v; +} + +template <typename T> +__attribute__((suppress_coverage)) +T +decr1 (T v) +{ + v += 1; /* count(#) */ + return v; /* count(#) */ +} + +template <typename T> +[[gnu::suppress_coverage]] +T +decr2 (T v) +{ + v += 1; /* count(#) */ + return v; /* count(#) */ +} + +template <typename T> +__attribute__((suppress_coverage)) +void +templated_function_level_class (T a) +{ + class C + { + public: + /* Function-level classes would still be counted. */ + explicit C (T e) : v (e) {} + T v; + }; + + C c (a); /* count(#) */ + ctor_x = 1; /* count(#) */ +} + +#pragma GCC suppress_coverage begin +template <typename T> +void +templated_function_level_class_pragma (T a) +{ + class C + { + public: + /* Function-level will now be suppressed. */ + explicit C (T e) : v (e) {} /* count (#) */ + T v; + }; + + C c (a); /* count(#) */ + ctor_x = 1; /* count(#) */ +} +#pragma GCC suppress_coverage end + +int main () +{ + try_catch1 (); + try_catch2 (); + try_catch3 (); + try_catch4 (); + try_catch5 (); + try_catch6 (); + try_catch7 (); + try_catch8 (); + try_catch9 (); + + try { ifelse_throw1 (1); } catch (...) {} + try { ifelse_throw1 (2); } catch (...) {} + + try { ifelse_throw2 (1); } catch (...) {} + try { ifelse_throw2 (2); } catch (...) {} + + try { ifelse_throw3 (1); } catch (...) {} + try { ifelse_throw3 (2); } catch (...) {} + + try { ifelse_throw4 (1); } catch (...) {} + try { ifelse_throw4 (2); } catch (...) {} + + ctor1 (); + ctor2 (5); + ctor3 (); + ctor4 (5); + + incr1 <int> (1); + incr1 <double> (2.0); + incr2 <int> (1); + incr2 <double> (2.0); + incr3 <int> (1); + incr3 <double> (3.0); + + decr1 <int> (1); + decr1 <double> (2.0); + decr2 <int> (1); + decr2 <double> (2.0); + + templated_function_level_class <int> (5); + templated_function_level_class <double> (5.0); + + templated_function_level_class_pragma <int> (5); + templated_function_level_class_pragma <double> (5.0); +} + +/* { 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 000000000000..2c96892e7efb --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-27.C @@ -0,0 +1,80 @@ +/* { dg-require-effective-target c++11 } */ +/* { dg-options "--coverage -std=c++11" } */ +/* { dg-do run } */ + +void lambda1 () +{ + /* From pr86109.C */ + auto partially_uncovered_lambda1 = [](int i) { /* count(1) */ +#pragma GCC suppress_coverage begin + if (i > 10) /* count(#) */ + return 0; /* count(#) */ +#pragma GCC suppress_coverage end + return 1; /* count(#####) */ + }; + + auto partially_uncovered_lambda2 = [](int i) { /* count(1) */ + if (i > 10) /* count(1) */ +#pragma GCC suppress_coverage begin + return 0; /* count(#) */ +#pragma GCC suppress_coverage end + return 1; /* count(#####) */ + }; + + partially_uncovered_lambda1 (20); + partially_uncovered_lambda2 (20); +} + +void lambda2 () +{ + /* From pr86109.C */ +#pragma GCC suppress_coverage begin + auto partially_uncovered_lambda1 = [](int i) { /* count(#) */ + if (i > 10) /* count(#) */ + return 0; /* count(#) */ +#pragma GCC suppress_coverage end + return 1; /* count(#####) */ + }; + + auto partially_uncovered_lambda2 = [](int i) { /* count(1) */ + if (i > 10) /* count(1) */ +#pragma GCC suppress_coverage begin + return 0; /* count(#) */ +#pragma GCC suppress_coverage end + return 1; /* count(#####) */ + }; + +#pragma GCC suppress_coverage begin + auto partially_uncovered_lambda3 = [](int i) { /* count(#) */ +#pragma GCC suppress_coverage end + if (i > 10) /* count(1) */ + return 0; /* count(1) */ + return 1; /* count(#####) */ + }; + + partially_uncovered_lambda1 (20); + partially_uncovered_lambda2 (20); + partially_uncovered_lambda3 (20); +} + +void lambda3 () +{ +#pragma GCC suppress_coverage begin + auto fully_covered_lambda1 = [](int i) { /* count(#) */ + if (i > 10) /* count(#) */ + return 0; /* count(#) */ + return 1; /* count(#) */ + }; +#pragma GCC suppress_coverage end + + fully_covered_lambda1 (20); +} + +int main () +{ + lambda1 (); + lambda2 (); + lambda3 (); +} + +/* { dg-final { run-gcov gcov-27.C } } */ diff --git a/gcc/testsuite/gcc.dg/pragma-suppress-coverage.c b/gcc/testsuite/gcc.dg/pragma-suppress-coverage.c new file mode 100644 index 000000000000..2a3bc42e8831 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pragma-suppress-coverage.c @@ -0,0 +1,32 @@ +/* Verify that we use emit diagnostics for #pragma GCC suppress_coverage. */ + +/* { dg-do assemble } */ +/* { dg-options "-fdiagnostics-show-caret" } */ + +#pragma GCC suppress_coverage end +/* { dg-warning "no matching begin for '#pragma GCC suppress_coverage end'" "" { target *-*-* } .-1 } + { dg-begin-multiline-output "" } + #pragma GCC suppress_coverage end + ^~~ + { dg-end-multiline-output "" } */ + +#pragma GCC suppress_coverage +/* { dg-warning "'#pragma GCC suppress_coverage' must be followed by 'begin' or 'end'" "" { target *-*-* } .-1 } + { dg-begin-multiline-output "" } + #pragma GCC suppress_coverage + ^~~ + { dg-end-multiline-output "" } */ + +#pragma GCC suppress_coverage begin more +/* { dg-warning "junk at end of '#pragma GCC suppress_coverage'" "" { target *-*-* } .-1 } + { dg-begin-multiline-output "" } + #pragma GCC suppress_coverage begin more + ^~~~ + { dg-end-multiline-output "" } */ + +#pragma GCC suppress_coverage begin +/* { dg-warning "'#pragma GCC suppress_coverage begin' was already in effect, ignored" "" { target *-*-* } .-1 } + { dg-begin-multiline-output "" } + #pragma GCC suppress_coverage begin + ^~~ + { dg-end-multiline-output "" } */ 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 000000000000..492a2f4551f6 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-37.c @@ -0,0 +1,1061 @@ +/* { dg-options "--coverage" } */ +/* { dg-do run } */ + +extern void exit (int); + +void noop () {} + +int do_something (int i) { return i; } + +/* Empty loop bodies should still suppress coverage for the for (;;) and compile + fine. */ +int +empty_body_for_loop () +{ + int i; +#pragma GCC suppress_coverage begin + for (i = 0; i < 10; i++) /* count(#) */ +#pragma GCC suppress_coverage end + ; + return i; +} + +/* Suppressing the loop should suppress everything within the loop, but not the + return. */ +int +ignored_for_loop () +{ + int i; +#pragma GCC suppress_coverage begin + for (i = 0; i < 10; i++) /* count(#) */ + { + noop (); /* count(#) */ + noop (); /* count(#) */ + } +#pragma GCC suppress_coverage end + + /* Making the for (;;) multi line should report count per line. */ +#pragma GCC suppress_coverage begin + for (i = 0; /* count(#) */ + i < 20; /* count(#) */ + i++) /* count(#) */ + { + noop (); /* count(#) */ + } +#pragma GCC suppress_coverage end + + noop (); + i++; + + return 0; /* count(1) */ +} + +int +declarations (int a) +{ + // Should work when it is the first declaration +#pragma GCC suppress_coverage begin + int b = a + 1; /* count(#) */ +#pragma GCC suppress_coverage end + + // declarations with no init has no, and suppress_coverage is a no-op +#pragma GCC suppress_coverage begin + int c; /* count(-) */ +#pragma GCC suppress_coverage end + + a *= 2; /* count(1) */ + + // Should work when it is not the first declaration +#pragma GCC suppress_coverage begin + int d = a - 1; /* count(#) */ +#pragma GCC suppress_coverage end + + c = a+b+d; /* count(1) */ + return c; +} + +int +compound_statements (int a) +{ + int c; /* count(-) */ +#pragma GCC suppress_coverage begin + { + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + } +#pragma GCC suppress_coverage end + return c; /* count(1) */ +} + +int +while1 (int a) +{ +#pragma GCC suppress_coverage begin + while (a > 0) /* count(#) */ + a = do_something (a - 1); /* count(#) */ +#pragma GCC suppress_coverage end + return a; +} + +/* If the pragma stops blocks from merging this would end up with the wrong + count. The while (cond) should run 6 times (a = 5), but earlier drafts + clocked it at 7 because an empty block at the end of the body would have + coverage suppressed, while the loop header did which prevented merging. */ +int +while2 (int a) +{ + while (a > 0) /* count(6) */ +#pragma GCC suppress_coverage begin + a = do_something (a - 1); /* count(#) */ +#pragma GCC suppress_coverage end + return a; +} + +int +dowhile1 (int a) +{ +#pragma GCC suppress_coverage begin + do + { + a = do_something (a - 1); /* count(#) */ + } while (a > 0); /* count(#) */ +#pragma GCC suppress_coverage end + return a; +} + +int +dowhile2 (int a) +{ + do + { +#pragma GCC suppress_coverage begin + a = do_something (a - 1); /* count(#) */ +#pragma GCC suppress_coverage end + } 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; +#pragma GCC suppress_coverage begin + for (;; a++) /* count(#) */ + { + int c[1]; + if (a) /* count(#) */ + { + break; /* count(#) */ + a; /* count(-) */ + continue; /* count(#) */ + } + continue; /* count(#) */ + } +#pragma GCC suppress_coverage end + + a = 0; + for (;; a++) /* count(1) */ + { + int c[1]; +#pragma GCC suppress_coverage begin + if (a) /* count(#) */ + { + break; /* count(#) */ + a; /* count(-) */ + continue; /* count(#) */ + } +#pragma GCC suppress_coverage end + continue; /* count(1) */ + } + + a = 0; + for (;; a++) /* count(1) */ + { + int c[1]; + if (a) /* count(2) */ + { +#pragma GCC suppress_coverage begin + break; /* count(#) */ +#pragma GCC suppress_coverage end + 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(-) */ +#pragma GCC suppress_coverage begin + continue; /* count(#) */ +#pragma GCC suppress_coverage end + } + continue; /* count(1) */ + } + + a = 0; + for (;; a++) /* count(1) */ + { + int c[1]; + if (a) /* count(2) */ + { + break; /* count(1) */ + a; /* count(-) */ + continue; /* count(1) */ + } +#pragma GCC suppress_coverage begin + continue; /* count(#) */ +#pragma GCC suppress_coverage end + } + + 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) */ +#pragma GCC suppress_coverage begin + if (acc > 10) /* count(#) */ + break; /* count(#) */ +#pragma GCC suppress_coverage end + 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. */ +#pragma GCC suppress_coverage begin + break; /* count(-) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + for (k = 0; k < o; k++) /* count(#) */ + for_temp++; /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + result = do_something (4); /* count(#) */ + else +#pragma GCC suppress_coverage end + result = do_something (1024); + /* We can ignore the ELSE. */ + else + if (j) /* count(2) */ + result = do_something (1); /* count(1) */ + else +#pragma GCC suppress_coverage begin + result = do_something (2); /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + if (j > 10) /* count(#) */ + result = do_something (result*4); /* count(#) */ +#pragma GCC suppress_coverage end + return result; /* count(5) */ +} + +int +test_ifelse2 (int i) +{ + int result = 0; +#pragma GCC suppress_coverage begin + if (!i) /* count(#) */ + result = do_something (1); /* count(#) */ +#pragma GCC suppress_coverage end + + if (i == 1) /* count(6) */ + result = do_something (1024); + + if (i == 2) /* count(6) */ +#pragma GCC suppress_coverage begin + result = do_something (2); /* count(#) */ +#pragma GCC suppress_coverage end + + if (i == 3) /* count(6) */ +#pragma GCC suppress_coverage begin + return do_something (8); /* count(#) */ +#pragma GCC suppress_coverage end + +#pragma GCC suppress_coverage begin + if (i == 4) /* count(#) */ + return do_something (2048); /* count(#) */ +#pragma GCC suppress_coverage end + + return result; /* count(4) */ +} + +int +test_ifelse3 (int i, int j) +{ + int result = 1; + /* Multi-condition ifs are suppressed, too */ +#pragma GCC suppress_coverage begin + if (i > 10 && j > i && j < 20) /* count(#) */ + result = do_something (16); /* count(#) */ +#pragma GCC suppress_coverage end + + if (i == 3 || j == 47 || i == j) /* count(11) */ +#pragma GCC suppress_coverage begin + result = do_something (64); /* count(#) */ +#pragma GCC suppress_coverage end + + return result; /* count(11) */ +} + +/* These are based on gcov-17.c */ +int +test_ifelse4 (int true_var, int false_var) +{ + unsigned int ret = 0; +#pragma GCC suppress_coverage begin + if (true_var) /* count(#) */ + { + if (false_var) /* count(#) */ + ret = 111; /* count(#) */ + } + else + ret = 999; /* count(#) */ +#pragma GCC suppress_coverage end + return ret; +} + +int +test_ifelse5 (int true_var, int false_var) +{ + unsigned int ret = 0; + if (true_var) /* count(1) */ +#pragma GCC suppress_coverage begin + { + if (false_var) /* count(#) */ + ret = 111; /* count(#) */ +#pragma GCC suppress_coverage end + } + else + ret = 999; /* count(#####) */ + return ret; +} + +int +test_ifelse6 (int true_var, int false_var) +{ + unsigned int ret = 0; + if (true_var) /* count(1) */ + { +#pragma GCC suppress_coverage begin + if (false_var) /* count(#) */ + ret = 111; /* count(#) */ +#pragma GCC suppress_coverage end + } + 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) */ +#pragma GCC suppress_coverage begin + ret = 111; /* count(#) */ +#pragma GCC suppress_coverage end + } + 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 +#pragma GCC suppress_coverage begin + ret = 999; /* count(#) */ +#pragma GCC suppress_coverage end + return ret; +} + +int +test_ifelse9 (int true_var, int false_var) +{ + unsigned int ret = 0; + /* With the pragma we can suppress condition, but still observe the then-else. */ +#pragma GCC suppress_coverage begin + if (true_var) /* count(#) */ +#pragma GCC suppress_coverage end + { + if (false_var) /* count(1) */ + ret = 111; /* count(#####) */ + } + else + 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); + test_ifelse9 (1, 0); +} + +int switch_val, switch_m; +int +test_switch (int i, int j) +{ + int result = 0; /* count(5) */ + /* We can suppress individual statements and breaks in the switch. */ + switch (i) /* count(5) */ + { + case 1: +#pragma GCC suppress_coverage begin + result = do_something (2); /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + break; /* count(#) */ +#pragma GCC suppress_coverage end + default: + result = do_something (32); /* count(1) */ +#pragma GCC suppress_coverage begin + switch_m++; /* count(#) */ +#pragma GCC suppress_coverage end + break; + } + + /* We can suppress the whole switch. */ +#pragma GCC suppress_coverage begin + 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; + } +#pragma GCC suppress_coverage end + + return result; /* count(4) */ +} + +int +test_switch2 (int i, int j) +{ + int result = 0; /* count(1) */ + switch (i) /* count(1) */ + { + case 1: + result = do_something (2); /* count(#####) */ + break; /* count(#####) */ + case 2: + result = do_something (1024); + break; + case 3: + case 4: + if (j == 2) /* count(#####) */ + return do_something (4); /* count(#####) */ + result = do_something (8); /* count(#####) */ + break; /* count(#####) */ + /* We can suppress the label itself with the pragma. */ +#pragma GCC suppress_coverage begin + default: /* count(#) */ + result = do_something (32); /* count(#) */ + switch_m++; /* count(#) */ + break; +#pragma GCC suppress_coverage end + } + + /* We can suppress multiple cases with a single pragma. */ +#pragma GCC suppress_coverage begin + switch (i) /* count(#) */ + { + case 1: /* count(#) */ + result = do_something (64); /* count(#) */ + break; /* count(#) */ + case 2: + result = do_something (128); /* count(#) */ + break; /* count(#) */ +#pragma GCC suppress_coverage end + case 3: + result = do_something (256); /* count(#####) */ + break; /* count(#####) */ + default: + result = do_something (512); /* count(1) */ + switch_m++; /* count(1) */ + break; + } + + return result; /* count(1) */ +} + +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; + switch_val += test_switch2 (16, 0); +} + +/* The goto tests from gcov-4.c. */ +int goto_val; + +int +test_goto1 (int f) +{ +#pragma GCC suppress_coverage begin + if (f) /* count(#) */ + goto lab1; /* count(#) */ +#pragma GCC suppress_coverage end + 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. + ; +#pragma GCC suppress_coverage begin + return 8; /* count(#) */ +#pragma GCC suppress_coverage end +} + +int +test_goto3 (int i, int j) +{ + if (j) /* count(1) */ +#pragma GCC suppress_coverage begin + goto else_; /* count(#) */ +#pragma GCC suppress_coverage end + +top: + if (i) /* count(1) */ + { + i = do_something (i); + } + else + { +else_: /* count(1) */ + j = do_something (j); /* count(2) */ +#pragma GCC suppress_coverage begin + if (j) /* count(#) */ + { + j = 0; /* count(#) */ + goto top; /* count(#) */ + } +#pragma GCC suppress_coverage end + } + 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 suppressed block. When jumping out, the + destination should still be counted. */ +#pragma GCC suppress_coverage begin + { + 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(#) */ + } +#pragma GCC suppress_coverage end + 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. */ +#pragma GCC suppress_coverage begin + noop (); goto baz; lab: a = do_something (a+1); /* count(#) */ +#pragma GCC suppress_coverage end + baz: + if (a == 1) /* count(2) */ + goto lab; /* count(1) */ + return a; +} + +int +test_goto6 (int a) +{ +#pragma GCC suppress_coverage begin + { + 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(#) */ + } +#pragma GCC suppress_coverage end + 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. + + This test demonstrates a surprising behaviour; because there are no braces + but a single return statement, the count is assigned to the pragma line and + not the return statement in the body. If we don't store the pragma location + we won't even record the return; as ignored. In a perfect world this would + be fixed by anchoring the return to the return statement itself and not the + if. If we add braces or return a value (see return2) this works as + expected. */ +void +return1 (int a, int b, int c) +{ +#pragma GCC suppress_coverage begin + if (a) return; /* count(#) */ +#pragma GCC suppress_coverage end +#pragma GCC suppress_coverage begin + if (do_something (b)) return; /* count(#) */ +#pragma GCC suppress_coverage end + if (do_something (c)) /* count(1) */ +#pragma GCC suppress_coverage begin /* count(#) */ + return; /* count(-) */ +#pragma GCC suppress_coverage end + if (do_something (c)) { /* count(#####) */ +#pragma GCC suppress_coverage begin /* count(-) */ + return; /* count(#) */ +#pragma GCC suppress_coverage end + } +} + +int +return2 (int a, int b, int c) +{ +#pragma GCC suppress_coverage begin + if (a) return a; /* count(#) */ +#pragma GCC suppress_coverage end /* count(-) */ +#pragma GCC suppress_coverage begin /* count(-) */ + if (do_something (b)) return b; /* count(#) */ +#pragma GCC suppress_coverage end /* count(-) */ + if (do_something (c)) /* count(1) */ +#pragma GCC suppress_coverage begin /* count(-) */ + return c; /* count(#) */ +#pragma GCC suppress_coverage end /* count(-) */ + return 0; +} + +void +call_return () +{ + return1 (1, 0, 0); + return1 (0, 1, 0); + return1 (0, 0, 1); + return2 (1, 0, 0); + return2 (0, 1, 0); + return2 (0, 0, 1); +} + +/* From gcov-6.c */ +int test_exit_val; + +void +test_exit1 (int i) +{ + /* An abnormal exit should not break suppression. */ +#pragma GCC suppress_coverage begin + if (i < 0) /* count(#) */ + exit (0); /* count(#) */ +#pragma GCC suppress_coverage end + test_exit_val += i; /* count(3) */ +} + +void +test_exit2 (int i) +{ + if (i < 0) /* count(4) */ +#pragma GCC suppress_coverage begin + exit (0); /* count(#) */ +#pragma GCC suppress_coverage end + test_exit_val += i; /* count(3) */ +} + +void +test_exit3 (int i) +{ + /* There can be statements on either side of exit (). */ +#pragma GCC suppress_coverage begin + if (i < 0) /* count(#) */ + { + test_exit_val += i; /* count(#) */ + exit (0); /* count(#) */ + test_exit_val += i; /* count(-) */ + } +#pragma GCC suppress_coverage end + 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) +{ + void *op; +#pragma GCC suppress_coverage begin + op = &&dest; /* count(#) */ +#pragma GCC suppress_coverage end +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: + ; +#pragma GCC suppress_coverage begin + if (op && a > 0) /* count(#) */ + { + a -= 1; /* count(#) */ + goto *op; /* count(#) */ + } +#pragma GCC suppress_coverage end + + return a; +} + +int +computed_goto3 (int a) +{ + void *op = &&dest; /* count(1) */ +dest: + ; + if (op && a > 0) /* count(6) */ + { + a -= 1; /* count(5) */ +#pragma GCC suppress_coverage begin + goto *op; /* count(#) */ +#pragma GCC suppress_coverage end + } + + return a; +} + +void +call_computed_goto () +{ + computed_goto1 (5); + computed_goto2 (5); + computed_goto3 (5); +} + +__attribute__((suppress_coverage)) int +suppressed_function (int a) +{ + int c; /* count(-) */ + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + return c; /* count(#) */ +} + +#pragma GCC suppress_coverage begin +int +pragma_begin_outside (int a) +{ + int c; /* count(-) */ + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + return c; /* count(#) */ +} +#pragma GCC suppress_coverage end + +#pragma GCC suppress_coverage begin +int +pragma_end_middle (int a) +{ + int c; /* count(-) */ + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ +#pragma GCC suppress_coverage end + int d = a - 1; /* count(1) */ + c = a+b+d; /* count(1) */ + return c; /* count(1) */ +} + +void +empty1 () +{ +#pragma GCC suppress_coverage begin +#pragma GCC suppress_coverage end +} + +#pragma GCC suppress_coverage begin +void +empty2 () +{ +#pragma GCC suppress_coverage end +} + +#pragma GCC suppress_coverage begin +void +empty3 () +{ +} +#pragma GCC suppress_coverage end + +__attribute__((suppress_coverage)) +void +empty4 () +{ +} + +void +call_empty () +{ + empty1 (); + empty2 (); + empty3 (); + empty4 (); +} + +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 (); + suppressed_function (1); + pragma_begin_outside (1); + pragma_end_middle (1); + call_empty (); + + /* The final test will actually exit, so make sure to call it last. */ + call_exit (); + return 0; +} + +/* { dg-final { run-gcov { 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 000000000000..1d54c41f7fee --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-38.c @@ -0,0 +1,23 @@ +/* { dg-options "--coverage -std=c23" } */ +/* { dg-do run } */ + +/* [[attribute]] syntax instead of __attribute__((attr)). */ + +[[gnu::suppress_coverage]] int +suppressed_function (int a) +{ + int c; /* count(-) */ + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + return c; /* count(#) */ +} + +int +main () +{ + suppressed_function (1); +} + +/* { 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 000000000000..1847db8e89c8 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-39.c @@ -0,0 +1,243 @@ +/* { dg-options "--coverage -fpath-coverage" } */ +/* { dg-do run } */ + +/* An obvious use case of #pragma GCC suppress_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-suppressed-branch: + + branch 0 taken 100% + branch 1 taken 0% +*/ + +int identity (int x) { return x; } + +#define REQUIRE(pred) do { \ + _Pragma ("GCC suppress_coverage begin") \ + if (!(pred)) return -1; \ + _Pragma ("GCC suppress_coverage end") \ + } while (0) +#define ENSURE(pred) do { \ + _Pragma ("GCC suppress_coverage begin") \ + if (!(pred)) return -1; \ + _Pragma ("GCC suppress_coverage end") \ + } while (0) + +/* BEGIN paths + summary: 1/1 + expect covered: 43(suppress) 48(suppress) 50 + + There are really 5 prime paths through this function, but 4 of them should + be suppressed. */ +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: 60(suppress) 65(suppress) 67 + + 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::suppress_coverage]] may change the graph (insert blocks), so the + number of paths may change slightly. */ +int +suppressed_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: 108 110 110(suppress) 116(true) 117 110 +*/ +int +suppressed_in_loop1 (int len) +/* END */ +{ + int x = len; + x = identity (x); + x *= 5; + for (int i = 0; i < len; ++i) + { +#pragma GCC suppress_coverage begin + x += identity (i); +#pragma GCC suppress_coverage end + + if (i > 5) + x += 1; + } + + return x; +} + +/* BEGIN paths + summary: 6/7 */ +int +suppressed_in_loop2 (int len) +/* END */ +{ + int x = len; + x = identity (x); + x *= 5; + for (int i = 0; i < len; ++i) + { + x += identity (i); + +#pragma GCC suppress_coverage begin + if (i > 5) + x += 1; +#pragma GCC suppress_coverage end + } + + 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 +suppressed_in_loop3 (int len) +/* END */ +{ + int x = len; + x = identity (x); + x *= 5; +#pragma GCC suppress_coverage begin + for (int i = 0; i < len; ++i) + { + x += identity (i); + + if (i > 5) + x += 1; + } +#pragma GCC suppress_coverage end + + return x; +} + +/* BEGIN paths + summary: 1/8 + args: (0, 0, 0, 0) + + Killing the first if should remove a single path only. */ +void +pathcov004c (int a, int b, int c, int d) +/* END */ +{ +#pragma GCC suppress_coverage begin + if (a) + { + } + else + /* We cannot syntactically put the pragma before the else, but since the + control flow is associated with the then-statement and not the else + keyword, putting the end after else works fine. */ +#pragma GCC suppress_coverage end + { + while (b-- > 0 && c-- > 0) + { + if (d) + break; + } + } +} + +/* BEGIN paths + args: (0, 1, 0, 0) + summary: 0/5 + + We keep five paths: + if (a) -> while -> if (d) -> exit + if (a) -> exit + + if (d) -> while -> if (d) + if (d) -> while -> exit + while -> if (d) -> while + + We really only disable the loop condition and not the implicit jumps. */ + +void +pathcov004d (int a, int b, int c, int d) +/* END */ +{ + if (a) + {} + else + { +#pragma GCC suppress_coverage begin + while (b-- > 0 && c-- > 0) +#pragma GCC suppress_coverage end + { + if (d) + break; + } + } +} + +int +main () +{ + contracts1 (2, 4); + contracts2 (-2, 4); + contracts2 (2, -4); + suppressed_in_loop (10); + suppressed_in_loop1 (10); + suppressed_in_loop2 (10); + suppressed_in_loop3 (10); + pathcov004c (0, 0, 0, 0); + pathcov004d (0, 1, 0, 0); +} + +/* { dg-final { run-gcov prime-paths { --prime-paths-lines=both gcov-39.c } } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-40.c b/gcc/testsuite/gcc.misc-tests/gcov-40.c new file mode 100644 index 000000000000..bb50541a7cdd --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-40.c @@ -0,0 +1,100 @@ +/* { dg-options "--coverage" } */ +/* { dg-do run } */ +/* { 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) { +#pragma GCC suppress_coverage begin + longjmp1_taken++; /* count(#) */ +#pragma GCC suppress_coverage end + 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) */ +#pragma GCC suppress_coverage begin + longjmp (longjmp1_env, 2); /* count(#) */ +#pragma GCC suppress_coverage end + } + longjmp1_bar (i); /* count(2) */ + longjmp1_bar (7); /* count(1) */ + longjmp1_val += 16; + longjmp1_foo_exit++; /* count(1) */ +} + +void +longjmp1 () +{ + int retlongjmp1_val; +#pragma GCC suppress_coverage begin + if ((retlongjmp1_val = setjmp (longjmp1_env))) { + longjmp1_val += retlongjmp1_val; /* count(#) */ + } +#pragma GCC suppress_coverage end + 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"); +#pragma GCC suppress_coverage begin + while (1) { + last = 1; /* count(#) */ + fjmp (); /* count(#) */ + } +#pragma GCC suppress_coverage end + } else { + __builtin_printf("False branch\n"); + } + + return 0; +} + +int main () +{ + longjmp1 (); + pr85372 (); +} + +/* { dg-final { run-gcov { gcov-40.c } } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-41.c b/gcc/testsuite/gcc.misc-tests/gcov-41.c new file mode 100644 index 000000000000..d00713da0140 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-41.c @@ -0,0 +1,35 @@ +/* { dg-do run } */ + +/* #pragma GCC suppress_coverage should be a no-op and harmless when coverage is + not enabled (--coverage, -fcondition-coverage, etc.). */ + +int do_something (int x) { + return x; +} + +int main (int argc, char **argv) +{ +#pragma GCC suppress_coverage begin + int b = argc + 1; +#pragma GCC suppress_coverage end + +#pragma GCC suppress_coverage begin + int c; +#pragma GCC suppress_coverage end + + int a = argc; + + if (a) + if (b) + { +#pragma GCC suppress_coverage begin + c = do_something (4); +#pragma GCC suppress_coverage end + } + else + c = do_something (1024); + +#pragma GCC suppress_coverage begin + int d = a + c - 1; +#pragma GCC suppress_coverage end +} diff --git a/gcc/testsuite/gcc.misc-tests/gcov-42.c b/gcc/testsuite/gcc.misc-tests/gcov-42.c new file mode 100644 index 000000000000..aa8da2909dad --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-42.c @@ -0,0 +1,43 @@ +/* { dg-options "--coverage" } */ +/* { dg-do run } */ + +/* We should be able to use the pragma even through a macro, also when that + macro is defined in a different file. */ +#define SUPPRESS_COVERAGE _Pragma ("GCC suppress_coverage begin") +#define ENABLE_COVERAGE _Pragma ("GCC suppress_coverage end") + +#include "gcov-42.h" + +int +fn1 (int a) +{ + int c; + SUPPRESS_COVERAGE + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + ENABLE_COVERAGE + return c; /* count(1) */ +} + +int +fn2 (int a) +{ + int c; + INCLUDE_SUPPRESS_COVERAGE + int b = a + 1; /* count(#) */ + a *= 2; /* count(#) */ + int d = a - 1; /* count(#) */ + c = a+b+d; /* count(#) */ + INCLUDE_ENABLE_COVERAGE + return c; /* count(1) */ +} + +int main () +{ + fn1 (1); + fn2 (2); +} + +/* { dg-final { run-gcov gcov-42.c } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-42.h b/gcc/testsuite/gcc.misc-tests/gcov-42.h new file mode 100644 index 000000000000..cf44168b4c9c --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-42.h @@ -0,0 +1,4 @@ +#define INCL_PUSH _Pragma ("GCC suppress_coverage begin") +#define INCL_POP _Pragma ("GCC suppress_coverage end") +#define INCLUDE_SUPPRESS_COVERAGE INCL_PUSH +#define INCLUDE_ENABLE_COVERAGE INCL_POP diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc index 15e026919461..ba1d4a125adc 100644 --- a/gcc/tree-cfg.cc +++ b/gcc/tree-cfg.cc @@ -133,6 +133,7 @@ static bool gimple_verify_flow_info (void); static void gimple_make_forwarder_block (edge); static bool verify_gimple_transaction (gtransaction *); static bool call_can_make_abnormal_goto (gimple *); +static bool gimple_empty_block_p (basic_block); /* Flowgraph optimization and cleanup. */ static void gimple_merge_blocks (basic_block, basic_block); @@ -492,6 +493,7 @@ make_blocks_1 (gimple_seq seq, basic_block bb) gimple *prev_stmt = NULL; bool start_new_block = true; bool first_stmt_of_seq = true; + bool suppress_coverage_p = false; while (!gsi_end_p (i)) { @@ -510,6 +512,13 @@ make_blocks_1 (gimple_seq seq, basic_block bb) if (stmt && is_gimple_call (stmt)) gimple_call_initialize_ctrl_altering (stmt); + suppress_coverage_p = in_pragma_suppress_coverage_p (stmt, + suppress_coverage_p); + if (suppress_coverage_p && !coverage_suppressed_p (bb)) + start_new_block = true; + else if (!suppress_coverage_p && coverage_suppressed_p (bb)) + start_new_block = true; + /* 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. */ @@ -518,6 +527,7 @@ make_blocks_1 (gimple_seq seq, basic_block bb) if (!first_stmt_of_seq) gsi_split_seq_before (&i, &seq); bb = create_basic_block (seq, bb); + start_new_block = false; prev_stmt = NULL; } @@ -526,6 +536,9 @@ make_blocks_1 (gimple_seq seq, basic_block bb) codes. */ gimple_set_bb (stmt, bb); + if (suppress_coverage_p) + suppress_coverage (bb); + /* If STMT is a basic block terminator, set START_NEW_BLOCK for the next iteration. */ if (stmt_ends_bb_p (stmt)) @@ -622,6 +635,44 @@ make_blocks (gimple_seq seq) } } + /* If the function decl itself is in a #pragma GCC suppress_coverage block we + insert a nop with the same location as the function so that we're + guaranteed that the first block gets coverage suppressed and the function + entry isn't counted. + + #pragma GCC suppress_coverage begin + int foo (args) // count = suppressed + { + #pragma GCC suppress_coverage end + ... + } + + If it isn't and the first stmt is suppressed we insert a nop to ensure that + the function entry is counted, but the first (real) stmt is not: + + int foo (args) // count = 1 + { + #pragma GCC suppress_coverage begin + bar (); // count = suppressed + #pragma GCC suppress_coverage end + ... + } + + gimple_can_merge_blocks_p is aware of specific instruction. */ + gimple_stmt_iterator gsi = gsi_start (seq); + const location_t decl_loc = DECL_SOURCE_LOCATION (cfun->decl); + if (location_in_pragma_suppress_coverage_p (decl_loc)) + { + gimple *nop = gimple_build_nop (); + gimple_set_location (nop, decl_loc); + gsi_insert_seq_before (&gsi, nop, GSI_NEW_STMT); + } + else if (*gsi && in_pragma_suppress_coverage_p (*gsi, false)) + { + gimple *nop = gimple_build_nop (); + gsi_insert_seq_before (&gsi, nop, GSI_NEW_STMT); + } + make_blocks_1 (seq, ENTRY_BLOCK_PTR_FOR_FN (cfun)); } @@ -1827,6 +1878,25 @@ gimple_can_merge_blocks_p (basic_block a, basic_block b) if (stmt && stmt_ends_bb_p (stmt)) return false; + /* Check if the compiler-inserted nop should block merges. */ + if (stmt && gimple_nop_p (stmt) + && gimple_location (stmt) == DECL_SOURCE_LOCATION (cfun->decl) + && coverage_suppressed_p (a) && !coverage_suppressed_p (b)) + return false; + + /* We cannot merge blocks if one has suppressed coverage and the other hasn't. + The exception is when the merge-into block is empty AND is the one with + coverage suppressed. This can happen for code like this: + + while (cond) + #pragma GCC suppress_coverage begin + foo (); + #pragma GCC suppress_coverage end + */ + if (coverage_suppressed_p (a) != coverage_suppressed_p (b) + && !(coverage_suppressed_p (a) && gimple_empty_block_p (a))) + 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)) @@ -1976,6 +2046,10 @@ gimple_merge_blocks (basic_block a, basic_block b) gimple_stmt_iterator last, gsi; gphi_iterator psi; + if (coverage_suppressed_p (a) && !coverage_suppressed_p (b) + && gimple_empty_block_p (a)) + suppress_coverage_unset (a); + if (dump_file) fprintf (dump_file, "Merging blocks %d and %d\n", a->index, b->index); @@ -2893,6 +2967,8 @@ gimple_split_edge (edge edge_in) new_bb = create_empty_bb (after_bb); new_bb->count = edge_in->count (); + if (coverage_suppressed_p (after_bb)) + suppress_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 @@ -6279,6 +6355,8 @@ gimple_split_block (basic_block bb, void *stmt) edge_iterator ei; new_bb = create_empty_bb (bb); + if (coverage_suppressed_p (bb)) + suppress_coverage (new_bb); /* Redirect the outgoing edges. */ new_bb->succs = bb->succs; @@ -6435,6 +6513,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_suppressed_p (bb)) + suppress_coverage (new_bb); /* Copy the PHI nodes. We ignore PHI node arguments here because the incoming edges have not been setup yet. */ @@ -9478,6 +9558,8 @@ insert_cond_bb (basic_block bb, gimple *stmt, gimple *cond, /* Create conditionally executed block. */ new_bb = create_empty_bb (bb); + if (coverage_suppressed_p (bb)) + suppress_coverage (new_bb); edge e = make_edge (bb, new_bb, EDGE_TRUE_VALUE); e->probability = prob; new_bb->count = e->count ();