From patchwork Wed Oct 5 07:47:59 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sharad Singhai X-Patchwork-Id: 117755 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) by ozlabs.org (Postfix) with SMTP id 3F4D5B6F95 for ; Wed, 5 Oct 2011 18:48:35 +1100 (EST) Received: (qmail 16297 invoked by alias); 5 Oct 2011 07:48:29 -0000 Received: (qmail 16276 invoked by uid 22791); 5 Oct 2011 07:48:22 -0000 X-SWARE-Spam-Status: No, hits=-1.5 required=5.0 tests=AWL, BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, RP_MATCHES_RCVD, SPF_HELO_PASS, TW_FN X-Spam-Check-By: sourceware.org Received: from smtp-out.google.com (HELO smtp-out.google.com) (74.125.121.67) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Wed, 05 Oct 2011 07:48:04 +0000 Received: from hpaq14.eem.corp.google.com (hpaq14.eem.corp.google.com [172.25.149.14]) by smtp-out.google.com with ESMTP id p957m2nb005186 for ; Wed, 5 Oct 2011 00:48:02 -0700 Received: from nabu.mtv.corp.google.com (nabu.mtv.corp.google.com [172.18.110.53]) by hpaq14.eem.corp.google.com with ESMTP id p957m0Rd007350 for ; Wed, 5 Oct 2011 00:48:00 -0700 Received: by nabu.mtv.corp.google.com (Postfix, from userid 68019) id 03AD4A0183; Wed, 5 Oct 2011 00:47:59 -0700 (PDT) To: gcc-patches@gcc.gnu.org Subject: Re: [PATCH] Add an intermediate coverage format for gcov Message-Id: <20111005074800.03AD4A0183@nabu.mtv.corp.google.com> Date: Wed, 5 Oct 2011 00:47:59 -0700 (PDT) From: singhai@google.com (Sharad Singhai) X-System-Of-Record: true Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Sorry about the badly formatted mail. Here is another version with a different mailer. -Sharad ---- This patch was earlier submitted to google/main, but I propose it for the trunk as well. This patch adds an intermediate coverage format (enabled via 'gcov -i'). This is a compact format as it does not require source files. The new option ('gcov -i') outputs .gcov files in an intermediate text format that can be postprocessed by lcov or other applications. It will output a single *.gcov file per *.gcda file. No source code is required. The format of the intermediate .gcov file is plain text with one entry per line SF:source_file_name FN:line_number,function_name FNDA:execution_count,function_name BA:line_num,branch_coverage_type DA:line number,execution_count Where the branch_coverage_type is 0 (Branch not executed) 1 (Branch executed, but not taken) 2 (Branch executed and taken) There can be multiple SF entries in an intermediate gcov file. All entries following SF pertain to that source file until the next SF entry. A concrete example looks like this: SF:array.c FN:4,sum FNDA:0,sum FN:13,main FNDA:1,main DA:4,2 BA:8,2 DA:7,1 I have bootstrapped and tested this patch on x86_64-linux. No new test failures were observed. Okay for trunk? Thanks, Sharad 2011-10-04 Sharad Singhai * doc/gcov.texi: Document gcov intermediate format. * gcov.c (print_usage): Handle new option. (process_args): Handle new option. (get_gcov_file_intermediate_name): New function. (output_intermediate_file): New function. (generate_results): Handle new option. * testsuite/lib/gcov.exp: Handle intermediate format. * testsuite/g++.dg/gcov/gcov-8.C: New testcase. Index: doc/gcov.texi =================================================================== --- doc/gcov.texi (revision 179475) +++ doc/gcov.texi (working copy) @@ -130,6 +130,7 @@ gcov [@option{-v}|@option{--version}] [@ [@option{-f}|@option{--function-summaries}] [@option{-o}|@option{--object-directory} @var{directory|file}] @var{sourcefiles} [@option{-u}|@option{--unconditional-branches}] + [@option{-i}|@option{--intermediate-format}] [@option{-d}|@option{--display-progress}] @c man end @c man begin SEEALSO @@ -216,6 +217,32 @@ Unconditional branches are normally not @itemx --display-progress Display the progress on the standard output. +@item -i +@itemx --intermediate-format +Output gcov file in an intermediate text format that can be used by +@command{lcov} or other applications. It will output a single *.gcov file per +*.gcda file. No source code is required. + +The format of the intermediate @file{.gcov} file is plain text with +one entry per line + +@smallexample +SF:@var{source_file_name} +FN:@var{line_number},@var{function_name} +FNDA:@var{execution_count},@var{function_name} +BA:@var{line_num},@var{branch_coverage_type} +DA:@var{line number},@var{execution_count} + +Where the @var{branch_coverage_type} is + 0 (Branch not executed) + 1 (Branch executed, but not taken) + 2 (Branch executed and taken) + +There can be multiple SF entries in an intermediate gcov file. All +entries following SF pertain to that source file until the next SF +entry. +@end smallexample + @end table @command{gcov} should be run with the current directory the same as that Index: gcov.c =================================================================== --- gcov.c (revision 179475) +++ gcov.c (working copy) @@ -39,6 +39,7 @@ along with Gcov; see the file COPYING3. #include "intl.h" #include "diagnostic.h" #include "version.h" +#include "demangle.h" #include @@ -311,6 +312,9 @@ static int flag_gcov_file = 1; static int flag_display_progress = 0; +/* Output *.gcov file in intermediate format used by 'lcov'. */ +static int flag_intermediate_format = 0; + /* For included files, make the gcov output file name include the name of the input source file. For example, if x.h is included in a.c, then the output file name is a.c##x.h.gcov instead of x.h.gcov. */ @@ -436,6 +440,11 @@ print_usage (int error_p) fnotice (file, " -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n"); fnotice (file, " -p, --preserve-paths Preserve all pathname components\n"); fnotice (file, " -u, --unconditional-branches Show unconditional branch counts too\n"); + fnotice (file, " -i, --intermediate-format Output .gcov file in an intermediate text\n\ + format that can be used by 'lcov' or other\n\ + applications. It will output a single\n\ + .gcov file per .gcda file. No source file\n\ + is required.\n"); fnotice (file, " -d, --display-progress Display progress information\n"); fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n", bug_report_url); @@ -472,6 +481,7 @@ static const struct option options[] = { "object-file", required_argument, NULL, 'o' }, { "unconditional-branches", no_argument, NULL, 'u' }, { "display-progress", no_argument, NULL, 'd' }, + { "intermediate-format", no_argument, NULL, 'i' }, { 0, 0, 0, 0 } }; @@ -482,7 +492,8 @@ process_args (int argc, char **argv) { int opt; - while ((opt = getopt_long (argc, argv, "abcdfhlno:puv", options, NULL)) != -1) + while ((opt = getopt_long (argc, argv, "abcdfhilno:puv", options, NULL)) != + -1) { switch (opt) { @@ -516,6 +527,10 @@ process_args (int argc, char **argv) case 'u': flag_unconditional = 1; break; + case 'i': + flag_intermediate_format = 1; + flag_gcov_file = 1; + break; case 'd': flag_display_progress = 1; break; @@ -531,6 +546,107 @@ process_args (int argc, char **argv) return optind; } +/* Get the name of the gcov file. The return value must be free'd. + + It appends the '.gcov' extension to the *basename* of the file. + The resulting file name will be in PWD. + + e.g., + input: foo.da, output: foo.da.gcov + input: a/b/foo.cc, output: foo.cc.gcov */ + +static char * +get_gcov_file_intermediate_name (const char *file_name) +{ + const char *gcov = ".gcov"; + char *result; + const char *cptr; + + /* Find the 'basename'. */ + cptr = lbasename (file_name); + + result = XNEWVEC(char, strlen (cptr) + strlen (gcov) + 1); + sprintf (result, "%s%s", cptr, gcov); + + return result; +} + +/* Output the result in intermediate format used by 'lcov'. + +The intermediate format contains a single file named 'foo.cc.gcov', +with no source code included. + +SF:/home/.../foo.h +DA:10,1 +DA:30,0 +DA:35,1 +SF:/home/.../bar.h +DA:12,0 +DA:33,0 +DA:55,1 +SF:/home/.../foo.cc +FN:30, +FNDA:2, +DA:42,0 +DA:53,1 +BA:55,1 +BA:55,2 +DA:95,1 +... + +The default format contains 3 separate files: 'foo.h.gcov', 'foo.cc.gcov', +'bar.h.gcov', each with source code included. */ + +static void +output_intermediate_file (FILE *gcov_file, source_t *src) +{ + unsigned line_num; /* current line number. */ + const line_t *line; /* current line info ptr. */ + function_t *fn; /* current function info ptr. */ + + fprintf (gcov_file, "SF:%s\n", src->name); /* source file name */ + + /* NOTE: 'gcov' sometimes output 2 extra lines (including 1 EOF + line) in the end. Likely related: + http://gcc.gnu.org/bugzilla/show_bug.cgi?id=15369 + */ + + for (fn = src->functions; fn; fn = fn->line_next) + { + char *demangled_name; + demangled_name = cplus_demangle (fn->name, DMGL_PARAMS); + /* FN:, */ + fprintf (gcov_file, "FN:%d,%s\n", fn->line, + demangled_name ? demangled_name : fn->name); + /* FNDA:, */ + fprintf (gcov_file, "FNDA:%s,%s\n", + format_gcov (fn->blocks[0].count, 0, -1), + demangled_name ? demangled_name : fn->name); + } + + for (line_num = 1, line = &src->lines[line_num]; + line_num < src->num_lines; + line_num++, line++) + { + arc_t *arc; + if (line->exists) + fprintf (gcov_file, "DA:%u,%d\n", line_num, + line->count != 0 ? 1 : 0); + if (flag_branches) + for (arc = line->u.branches; arc; arc = arc->line_next) + { + /* BA:, + branch_coverage_type: 0 (Branch not executed) + : 1 (Branch executed, but not taken) + : 2 (Branch executed and taken) + */ + if (!arc->is_unconditional && !arc->is_call_non_return) + fprintf(gcov_file, "BA:%d,%d\n", line_num, + arc->src->count ? (arc->count > 0) + 1 : 0); + } + } +} + /* Process a single source file. */ static void @@ -570,6 +686,8 @@ generate_results (const char *file_name) { source_t *src; function_t *fn; + FILE *gcov_file_intermediate = NULL; + char *gcov_file_intermediate_name = NULL; for (src = sources; src; src = src->next) src->lines = XCNEWVEC (line_t, src->num_lines); @@ -587,31 +705,55 @@ generate_results (const char *file_name) } } + if (flag_gcov_file && flag_intermediate_format) + { + /* Open the intermediate file. */ + gcov_file_intermediate_name = + get_gcov_file_intermediate_name (file_name); + gcov_file_intermediate = fopen (gcov_file_intermediate_name, "w"); + } for (src = sources; src; src = src->next) { accumulate_line_counts (src); function_summary (&src->coverage, "File"); if (flag_gcov_file) { - char *gcov_file_name = make_gcov_file_name (file_name, src->name); - FILE *gcov_file = fopen (gcov_file_name, "w"); - - if (gcov_file) - { - fnotice (stdout, "%s:creating '%s'\n", - src->name, gcov_file_name); - output_lines (gcov_file, src); - if (ferror (gcov_file)) - fnotice (stderr, "%s:error writing output file '%s'\n", - src->name, gcov_file_name); - fclose (gcov_file); - } - else - fnotice (stderr, "%s:could not open output file '%s'\n", - src->name, gcov_file_name); - free (gcov_file_name); - } - fnotice (stdout, "\n"); + if (flag_intermediate_format) + /* Now output in the intermediate format without requiring + source files. This outputs a section to a *single* file. */ + output_intermediate_file (gcov_file_intermediate, src); + else + { + /* Now output the version with source files. + This outputs a separate *.gcov file for each source file + involved. */ + char *gcov_file_name = make_gcov_file_name (file_name, src->name); + FILE *gcov_file = fopen (gcov_file_name, "w"); + + if (gcov_file) + { + fnotice (stdout, "%s:creating '%s'\n", + src->name, gcov_file_name); + output_lines (gcov_file, src); + if (ferror (gcov_file)) + fnotice (stderr, "%s:error writing output file '%s'\n", + src->name, gcov_file_name); + fclose (gcov_file); + } + else + fnotice (stderr, "%s:could not open output file '%s'\n", + src->name, gcov_file_name); + free (gcov_file_name); + } + fnotice (stdout, "\n"); + } + } + + if (flag_gcov_file && flag_intermediate_format) + { + /* Now we've finished writing the intermediate file. */ + fclose (gcov_file_intermediate); + XDELETEVEC (gcov_file_intermediate_name); } } Index: testsuite/lib/gcov.exp =================================================================== --- testsuite/lib/gcov.exp (revision 179475) +++ testsuite/lib/gcov.exp (working copy) @@ -60,6 +60,59 @@ proc verify-lines { testcase file } { } # +# verify-intermediate -- check that intermediate file has certain lines +# +# TESTCASE is the name of the test. +# FILE is the name of the gcov output file. +# +# Checks are very loose, they are based on being certain tags present +# in the output. They do not check for exact expected execution +# counts. For that the regular gcov format should be checked. +# +proc verify-intermediate { testcase file } { + set failed 0 + set sf 0 + set fn 0 + set fnda 0 + set da 0 + set fd [open $file r] + while { [gets $fd line] >= 0 } { + if [regexp "^SF:" $line] { + incr sf + } + if [regexp "^FN:(\[0-9\]+)," $line] { + incr fn + } + if [regexp "^FNDA:(\[0-9\]+)," $line] { + incr fnda + } + if [regexp "^DA:(\[0-9\]+),(\[0-9\]+)" $line] { + incr da + } + } + + # We should see at least one tag of each type + if {$sf == 0} { + fail "expected SF: not found" + incr failed + } + if {$fn == 0} { + fail "expected FN: not found" + incr failed + } + if {$fnda == 0} { + fail "expected FNDA: not found" + incr failed + } + if {$da == 0} { + fail "expected DA: not found" + incr failed + } + return $failed +} + + +# # verify-branches -- check that branch percentages are as expected # # TESTCASE is the name of the test. @@ -234,6 +287,8 @@ proc run-gcov { args } { set gcov_verify_calls 0 set gcov_verify_branches 0 + set gcov_verify_lines 1 + set gcov_verify_intermediate 0 set gcov_execute_xfail "" set gcov_verify_xfail "" @@ -242,6 +297,11 @@ proc run-gcov { args } { set gcov_verify_calls 1 } elseif { $a == "branches" } { set gcov_verify_branches 1 + } elseif { $a == "intermediate" } { + set gcov_verify_intermediate 1 + set gcov_verify_calls 0 + set gcov_verify_branches 0 + set gcov_verify_lines 0 } } @@ -274,8 +334,12 @@ proc run-gcov { args } { eval setup_xfail [split $gcov_verify_xfail] } - # Check that line execution counts are as expected. - set lfailed [verify-lines $testcase $testcase.gcov] + if { $gcov_verify_lines } { + # Check that line execution counts are as expected. + set lfailed [verify-lines $testcase $testcase.gcov] + } else { + set lfailed 0 + } # If requested via the .x file, check that branch and call information # is correct. @@ -289,12 +353,18 @@ proc run-gcov { args } { } else { set cfailed 0 } + if { $gcov_verify_intermediate } { + # Check that intermediate format has the expected format + set ifailed [verify-intermediate $testcase $testcase.gcov] + } else { + set ifailed 0 + } # Report whether the gcov test passed or failed. If there were # multiple failures then the message is a summary. - set tfailed [expr $lfailed + $bfailed + $cfailed] + set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed] if { $tfailed > 0 } { - fail "$subdir/$testcase gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages" + fail "$subdir/$testcase gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format" } else { pass "$subdir/$testcase gcov" clean-gcov $testcase Index: testsuite/g++.dg/gcov/gcov-8.C =================================================================== --- testsuite/g++.dg/gcov/gcov-8.C (revision 0) +++ testsuite/g++.dg/gcov/gcov-8.C (revision 0) @@ -0,0 +1,32 @@ +/* Verify that intermediate coverage format can be generated for simple code. */ + +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +class C { +public: + C() + { + i = 0; /* count(1) */ + } + ~C() {} + void seti (int j) + { + i = j; /* count(1) */ + } +private: + int i; +}; + +void foo() +{ + C c; /* count(2) */ + c.seti (1); /* count(1) */ +} + +int main() +{ + foo(); /* count(1) */ +} + +/* { dg-final { run-gcov intermediate { -i gcov-8.C } } } */