diff options
Diffstat (limited to 'chromium/third_party/lcov-1.9/bin/geninfo')
-rwxr-xr-x | chromium/third_party/lcov-1.9/bin/geninfo | 3068 |
1 files changed, 3068 insertions, 0 deletions
diff --git a/chromium/third_party/lcov-1.9/bin/geninfo b/chromium/third_party/lcov-1.9/bin/geninfo new file mode 100755 index 00000000000..dcb1a678186 --- /dev/null +++ b/chromium/third_party/lcov-1.9/bin/geninfo @@ -0,0 +1,3068 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002,2010 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# geninfo +# +# This script generates .info files from data files as created by code +# instrumented with gcc's built-in profiling mechanism. Call it with +# --help and refer to the geninfo man page to get information on usage +# and available options. +# +# +# Authors: +# 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> +# IBM Lab Boeblingen +# based on code by Manoj Iyer <manjo@mail.utexas.edu> and +# Megan Bock <mbock@us.ibm.com> +# IBM Austin +# 2002-09-05 / Peter Oberparleiter: implemented option that allows file list +# 2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also +# parse the new gcov format which is to be introduced in gcc 3.3 +# 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT +# 2003-07-03 / Peter Oberparleiter: added line checksum support, added +# --no-checksum +# 2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV +# 2003-12-11 / Laurent Deniel: added --follow option +# workaround gcov (<= 3.2.x) bug with empty .da files +# 2004-01-03 / Laurent Deniel: Ignore empty .bb files +# 2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and +# gcov versioning +# 2004-08-09 / Peter Oberparleiter: added configuration file support +# 2008-07-14 / Tom Zoerner: added --function-coverage command line option +# 2008-08-13 / Peter Oberparleiter: modified function coverage +# implementation (now enabled per default) +# + +use strict; +use File::Basename; +use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir + splitpath/; +use Getopt::Long; +use Digest::MD5 qw(md5_base64); + + +# Constants +our $lcov_version = 'LCOV version 1.9'; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $gcov_tool = "gcov"; +our $tool_name = basename($0); + +our $GCOV_VERSION_3_4_0 = 0x30400; +our $GCOV_VERSION_3_3_0 = 0x30300; +our $GCNO_FUNCTION_TAG = 0x01000000; +our $GCNO_LINES_TAG = 0x01450000; +our $GCNO_FILE_MAGIC = 0x67636e6f; +our $BBG_FILE_MAGIC = 0x67626267; + +our $COMPAT_HAMMER = "hammer"; + +our $ERROR_GCOV = 0; +our $ERROR_SOURCE = 1; +our $ERROR_GRAPH = 2; + +our $EXCL_START = "LCOV_EXCL_START"; +our $EXCL_STOP = "LCOV_EXCL_STOP"; +our $EXCL_LINE = "LCOV_EXCL_LINE"; + +our $BR_LINE = 0; +our $BR_BLOCK = 1; +our $BR_BRANCH = 2; +our $BR_TAKEN = 3; +our $BR_VEC_ENTRIES = 4; +our $BR_VEC_WIDTH = 32; + +our $UNNAMED_BLOCK = 9999; + +# Prototypes +sub print_usage(*); +sub gen_info($); +sub process_dafile($$); +sub match_filename($@); +sub solve_ambiguous_match($$$); +sub split_filename($); +sub solve_relative_path($$); +sub read_gcov_header($); +sub read_gcov_file($); +sub info(@); +sub get_gcov_version(); +sub system_no_output($@); +sub read_config($); +sub apply_config($); +sub get_exclusion_data($); +sub apply_exclusion_data($$); +sub process_graphfile($$); +sub filter_fn_name($); +sub warn_handler($); +sub die_handler($); +sub graph_error($$); +sub graph_expect($); +sub graph_read(*$;$); +sub graph_skip(*$;$); +sub sort_uniq(@); +sub sort_uniq_lex(@); +sub graph_cleanup($); +sub graph_find_base($); +sub graph_from_bb($$$); +sub graph_add_order($$$); +sub read_bb_word(*;$); +sub read_bb_value(*;$); +sub read_bb_string(*$); +sub read_bb($$); +sub read_bbg_word(*;$); +sub read_bbg_value(*;$); +sub read_bbg_string(*); +sub read_bbg_lines_record(*$$$$$$); +sub read_bbg($$); +sub read_gcno_word(*;$); +sub read_gcno_value(*$;$); +sub read_gcno_string(*$); +sub read_gcno_lines_record(*$$$$$$$); +sub read_gcno_function_record(*$$$$); +sub read_gcno($$); +sub get_gcov_capabilities(); +sub get_overall_line($$$$); +sub print_overall_rate($$$$$$$$$); +sub br_gvec_len($); +sub br_gvec_get($$); +sub debug($); +sub int_handler(); + + +# Global variables +our $gcov_version; +our $graph_file_extension; +our $data_file_extension; +our @data_directory; +our $test_name = ""; +our $quiet; +our $help; +our $output_filename; +our $base_directory; +our $version; +our $follow; +our $checksum; +our $no_checksum; +our $compat_libtool; +our $no_compat_libtool; +our $adjust_testname; +our $config; # Configuration file contents +our $compatibility; # Compatibility version flag - used to indicate + # non-standard GCOV data format versions +our @ignore_errors; # List of errors to ignore (parameter) +our @ignore; # List of errors to ignore (array) +our $initial; +our $no_recursion = 0; +our $maxdepth; +our $no_markers = 0; +our $opt_derive_func_data = 0; +our $debug = 0; +our $gcov_caps; +our @gcov_options; + +our $cwd = `pwd`; +chomp($cwd); + + +# +# Code entry point +# + +# Register handler routine to be called when interrupted +$SIG{"INT"} = \&int_handler; +$SIG{__WARN__} = \&warn_handler; +$SIG{__DIE__} = \&die_handler; + +# Prettify version string +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; + +# Set LANG so that gcov output will be in a unified format +$ENV{"LANG"} = "C"; + +# Read configuration file if available +if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +{ + $config = read_config($ENV{"HOME"}."/.lcovrc"); +} +elsif (-r "/etc/lcovrc") +{ + $config = read_config("/etc/lcovrc"); +} + +if ($config) +{ + # Copy configuration file values to variables + apply_config({ + "geninfo_gcov_tool" => \$gcov_tool, + "geninfo_adjust_testname" => \$adjust_testname, + "geninfo_checksum" => \$checksum, + "geninfo_no_checksum" => \$no_checksum, # deprecated + "geninfo_compat_libtool" => \$compat_libtool}); + + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } +} + +# Parse command line options +if (!GetOptions("test-name|t=s" => \$test_name, + "output-filename|o=s" => \$output_filename, + "checksum" => \$checksum, + "no-checksum" => \$no_checksum, + "base-directory|b=s" => \$base_directory, + "version|v" =>\$version, + "quiet|q" => \$quiet, + "help|h|?" => \$help, + "follow|f" => \$follow, + "compat-libtool" => \$compat_libtool, + "no-compat-libtool" => \$no_compat_libtool, + "gcov-tool=s" => \$gcov_tool, + "ignore-errors=s" => \@ignore_errors, + "initial|i" => \$initial, + "no-recursion" => \$no_recursion, + "no-markers" => \$no_markers, + "derive-func-data" => \$opt_derive_func_data, + "debug" => \$debug, + )) +{ + print(STDERR "Use $tool_name --help to get usage information\n"); + exit(1); +} +else +{ + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } + + if (defined($no_compat_libtool)) + { + $compat_libtool = ($no_compat_libtool ? 0 : 1); + $no_compat_libtool = undef; + } +} + +@data_directory = @ARGV; + +# Check for help option +if ($help) +{ + print_usage(*STDOUT); + exit(0); +} + +# Check for version option +if ($version) +{ + print("$tool_name: $lcov_version\n"); + exit(0); +} + +# Make sure test names only contain valid characters +if ($test_name =~ s/\W/_/g) +{ + warn("WARNING: invalid characters removed from testname!\n"); +} + +# Adjust test name to include uname output if requested +if ($adjust_testname) +{ + $test_name .= "__".`uname -a`; + $test_name =~ s/\W/_/g; +} + +# Make sure base_directory contains an absolute path specification +if ($base_directory) +{ + $base_directory = solve_relative_path($cwd, $base_directory); +} + +# Check for follow option +if ($follow) +{ + $follow = "-follow" +} +else +{ + $follow = ""; +} + +# Determine checksum mode +if (defined($checksum)) +{ + # Normalize to boolean + $checksum = ($checksum ? 1 : 0); +} +else +{ + # Default is off + $checksum = 0; +} + +# Determine libtool compatibility mode +if (defined($compat_libtool)) +{ + $compat_libtool = ($compat_libtool? 1 : 0); +} +else +{ + # Default is on + $compat_libtool = 1; +} + +# Determine max depth for recursion +if ($no_recursion) +{ + $maxdepth = "-maxdepth 1"; +} +else +{ + $maxdepth = ""; +} + +# Check for directory name +if (!@data_directory) +{ + die("No directory specified\n". + "Use $tool_name --help to get usage information\n"); +} +else +{ + foreach (@data_directory) + { + stat($_); + if (!-r _) + { + die("ERROR: cannot read $_!\n"); + } + } +} + +if (@ignore_errors) +{ + my @expanded; + my $error; + + # Expand comma-separated entries + foreach (@ignore_errors) { + if (/,/) + { + push(@expanded, split(",", $_)); + } + else + { + push(@expanded, $_); + } + } + + foreach (@expanded) + { + /^gcov$/ && do { $ignore[$ERROR_GCOV] = 1; next; } ; + /^source$/ && do { $ignore[$ERROR_SOURCE] = 1; next; }; + /^graph$/ && do { $ignore[$ERROR_GRAPH] = 1; next; }; + die("ERROR: unknown argument for --ignore-errors: $_\n"); + } +} + +if (system_no_output(3, $gcov_tool, "--help") == -1) +{ + die("ERROR: need tool $gcov_tool!\n"); +} + +$gcov_version = get_gcov_version(); + +if ($gcov_version < $GCOV_VERSION_3_4_0) +{ + if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + { + $data_file_extension = ".da"; + $graph_file_extension = ".bbg"; + } + else + { + $data_file_extension = ".da"; + $graph_file_extension = ".bb"; + } +} +else +{ + $data_file_extension = ".gcda"; + $graph_file_extension = ".gcno"; +} + +# Determine gcov options +$gcov_caps = get_gcov_capabilities(); +push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'}); +push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'}); +push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'}); +push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'}); + +# Check output filename +if (defined($output_filename) && ($output_filename ne "-")) +{ + # Initially create output filename, data is appended + # for each data file processed + local *DUMMY_HANDLE; + open(DUMMY_HANDLE, ">$output_filename") + or die("ERROR: cannot create $output_filename!\n"); + close(DUMMY_HANDLE); + + # Make $output_filename an absolute path because we're going + # to change directories while processing files + if (!($output_filename =~ /^\/(.*)$/)) + { + $output_filename = $cwd."/".$output_filename; + } +} + +# Do something +foreach my $entry (@data_directory) { + gen_info($entry); +} + +if ($initial) { + warn("Note: --initial does not generate branch coverage ". + "data\n"); +} +info("Finished .info-file creation\n"); + +exit(0); + + + +# +# print_usage(handle) +# +# Print usage information. +# + +sub print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE <<END_OF_USAGE); +Usage: $tool_name [OPTIONS] DIRECTORY + +Traverse DIRECTORY and create a .info file for each data file found. Note +that you may specify more than one directory, all of which are then processed +sequentially. + + -h, --help Print this help, then exit + -v, --version Print version number, then exit + -q, --quiet Do not print progress messages + -i, --initial Capture initial zero coverage data + -t, --test-name NAME Use test case name NAME for resulting data + -o, --output-filename OUTFILE Write data only to OUTFILE + -f, --follow Follow links when searching .da/.gcda files + -b, --base-directory DIR Use DIR as base directory for relative paths + --(no-)checksum Enable (disable) line checksumming + --(no-)compat-libtool Enable (disable) libtool compatibility mode + --gcov-tool TOOL Specify gcov tool location + --ignore-errors ERROR Continue after ERROR (gcov, source, graph) + --no-recursion Exclude subdirectories from processing + --function-coverage Capture function call counts + --no-markers Ignore exclusion markers in source code + --derive-func-data Generate function data from line data + +For more information see: $lcov_url +END_OF_USAGE + ; +} + +# +# get_common_prefix(min_dir, filenames) +# +# Return the longest path prefix shared by all filenames. MIN_DIR specifies +# the minimum number of directories that a filename may have after removing +# the prefix. +# + +sub get_common_prefix($@) +{ + my ($min_dir, @files) = @_; + my $file; + my @prefix; + my $i; + + foreach $file (@files) { + my ($v, $d, $f) = splitpath($file); + my @comp = splitdir($d); + + if (!@prefix) { + @prefix = @comp; + next; + } + for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) { + if ($comp[$i] ne $prefix[$i] || + ((scalar(@comp) - ($i + 1)) <= $min_dir)) { + delete(@prefix[$i..scalar(@prefix)]); + last; + } + } + } + + return catdir(@prefix); +} + +# +# gen_info(directory) +# +# Traverse DIRECTORY and create a .info file for each data file found. +# The .info file contains TEST_NAME in the following format: +# +# TN:<test name> +# +# For each source file name referenced in the data file, there is a section +# containing source code and coverage data: +# +# SF:<absolute path to the source file> +# FN:<line number of function start>,<function name> for each function +# DA:<line number>,<execution count> for each instrumented line +# LH:<number of lines with an execution count> greater than 0 +# LF:<number of instrumented lines> +# +# Sections are separated by: +# +# end_of_record +# +# In addition to the main source code file there are sections for each +# #included file containing executable code. Note that the absolute path +# of a source file is generated by interpreting the contents of the respective +# graph file. Relative filenames are prefixed with the directory in which the +# graph file is found. Note also that symbolic links to the graph file will be +# resolved so that the actual file path is used instead of the path to a link. +# This approach is necessary for the mechanism to work with the /proc/gcov +# files. +# +# Die on error. +# + +sub gen_info($) +{ + my $directory = $_[0]; + my @file_list; + my $file; + my $prefix; + my $type; + my $ext; + + if ($initial) { + $type = "graph"; + $ext = $graph_file_extension; + } else { + $type = "data"; + $ext = $data_file_extension; + } + + if (-d $directory) + { + info("Scanning $directory for $ext files ...\n"); + + @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`; + chomp(@file_list); + @file_list or + die("ERROR: no $ext files found in $directory!\n"); + $prefix = get_common_prefix(1, @file_list); + info("Found %d %s files in %s\n", $#file_list+1, $type, + $directory); + } + else + { + @file_list = ($directory); + $prefix = ""; + } + + # Process all files in list + foreach $file (@file_list) { + # Process file + if ($initial) { + process_graphfile($file, $prefix); + } else { + process_dafile($file, $prefix); + } + } +} + + +sub derive_data($$$) +{ + my ($contentdata, $funcdata, $bbdata) = @_; + my @gcov_content = @{$contentdata}; + my @gcov_functions = @{$funcdata}; + my %fn_count; + my %ln_fn; + my $line; + my $maxline; + my %fn_name; + my $fn; + my $count; + + if (!defined($bbdata)) { + return @gcov_functions; + } + + # First add existing function data + while (@gcov_functions) { + $count = shift(@gcov_functions); + $fn = shift(@gcov_functions); + + $fn_count{$fn} = $count; + } + + # Convert line coverage data to function data + foreach $fn (keys(%{$bbdata})) { + my $line_data = $bbdata->{$fn}; + my $line; + + if ($fn eq "") { + next; + } + # Find the lowest line count for this function + $count = 0; + foreach $line (@$line_data) { + my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ]; + + if (($lcount > 0) && + (($count == 0) || ($lcount < $count))) { + $count = $lcount; + } + } + $fn_count{$fn} = $count; + } + + + # Check if we got data for all functions + foreach $fn (keys(%fn_name)) { + if ($fn eq "") { + next; + } + if (defined($fn_count{$fn})) { + next; + } + warn("WARNING: no derived data found for function $fn\n"); + } + + # Convert hash to list in @gcov_functions format + foreach $fn (sort(keys(%fn_count))) { + push(@gcov_functions, $fn_count{$fn}, $fn); + } + + return @gcov_functions; +} + +# +# get_filenames(directory, pattern) +# +# Return a list of filenames found in directory which match the specified +# pattern. +# +# Die on error. +# + +sub get_filenames($$) +{ + my ($dirname, $pattern) = @_; + my @result; + my $directory; + local *DIR; + + opendir(DIR, $dirname) or + die("ERROR: cannot read directory $dirname\n"); + while ($directory = readdir(DIR)) { + push(@result, $directory) if ($directory =~ /$pattern/); + } + closedir(DIR); + + return @result; +} + +# +# process_dafile(da_filename, dir) +# +# Create a .info file for a single data file. +# +# Die on error. +# + +sub process_dafile($$) +{ + my ($file, $dir) = @_; + my $da_filename; # Name of data file to process + my $da_dir; # Directory of data file + my $source_dir; # Directory of source file + my $da_basename; # data filename without ".da/.gcda" extension + my $bb_filename; # Name of respective graph file + my $bb_basename; # Basename of the original graph file + my $graph; # Contents of graph file + my $instr; # Contents of graph file part 2 + my $gcov_error; # Error code of gcov tool + my $object_dir; # Directory containing all object files + my $source_filename; # Name of a source code file + my $gcov_file; # Name of a .gcov file + my @gcov_content; # Content of a .gcov file + my $gcov_branches; # Branch content of a .gcov file + my @gcov_functions; # Function calls of a .gcov file + my @gcov_list; # List of generated .gcov files + my $line_number; # Line number count + my $lines_hit; # Number of instrumented lines hit + my $lines_found; # Number of instrumented lines found + my $funcs_hit; # Number of instrumented functions hit + my $funcs_found; # Number of instrumented functions found + my $br_hit; + my $br_found; + my $source; # gcov source header information + my $object; # gcov object header information + my @matches; # List of absolute paths matching filename + my @unprocessed; # List of unprocessed source code files + my $base_dir; # Base directory for current file + my @tmp_links; # Temporary links to be cleaned up + my @result; + my $index; + my $da_renamed; # If data file is to be renamed + local *INFO_HANDLE; + + info("Processing %s\n", abs2rel($file, $dir)); + # Get path to data file in absolute and normalized form (begins with /, + # contains no more ../ or ./) + $da_filename = solve_relative_path($cwd, $file); + + # Get directory and basename of data file + ($da_dir, $da_basename) = split_filename($da_filename); + + # avoid files from .libs dirs + if ($compat_libtool && $da_dir =~ m/(.*)\/\.libs$/) { + $source_dir = $1; + } else { + $source_dir = $da_dir; + } + + if (-z $da_filename) + { + $da_renamed = 1; + } + else + { + $da_renamed = 0; + } + + # Construct base_dir for current file + if ($base_directory) + { + $base_dir = $base_directory; + } + else + { + $base_dir = $source_dir; + } + + # Check for writable $base_dir (gcov will try to write files there) + stat($base_dir); + if (!-w _) + { + die("ERROR: cannot write to directory $base_dir!\n"); + } + + # Construct name of graph file + $bb_basename = $da_basename.$graph_file_extension; + $bb_filename = "$da_dir/$bb_basename"; + + # Find out the real location of graph file in case we're just looking at + # a link + while (readlink($bb_filename)) + { + my $last_dir = dirname($bb_filename); + + $bb_filename = readlink($bb_filename); + $bb_filename = solve_relative_path($last_dir, $bb_filename); + } + + # Ignore empty graph file (e.g. source file with no statement) + if (-z $bb_filename) + { + warn("WARNING: empty $bb_filename (skipped)\n"); + return; + } + + # Read contents of graph file into hash. We need it later to find out + # the absolute path to each .gcov file created as well as for + # information about functions and their source code positions. + if ($gcov_version < $GCOV_VERSION_3_4_0) + { + if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + { + ($instr, $graph) = read_bbg($bb_filename, $base_dir); + } + else + { + ($instr, $graph) = read_bb($bb_filename, $base_dir); + } + } + else + { + ($instr, $graph) = read_gcno($bb_filename, $base_dir); + } + + # Set $object_dir to real location of object files. This may differ + # from $da_dir if the graph file is just a link to the "real" object + # file location. + $object_dir = dirname($bb_filename); + + # Is the data file in a different directory? (this happens e.g. with + # the gcov-kernel patch) + if ($object_dir ne $da_dir) + { + # Need to create link to data file in $object_dir + system("ln", "-s", $da_filename, + "$object_dir/$da_basename$data_file_extension") + and die ("ERROR: cannot create link $object_dir/". + "$da_basename$data_file_extension!\n"); + push(@tmp_links, + "$object_dir/$da_basename$data_file_extension"); + # Need to create link to graph file if basename of link + # and file are different (CONFIG_MODVERSION compat) + if ((basename($bb_filename) ne $bb_basename) && + (! -e "$object_dir/$bb_basename")) { + symlink($bb_filename, "$object_dir/$bb_basename") or + warn("WARNING: cannot create link ". + "$object_dir/$bb_basename\n"); + push(@tmp_links, "$object_dir/$bb_basename"); + } + } + + # Change to directory containing data files and apply GCOV + chdir($base_dir); + + if ($da_renamed) + { + # Need to rename empty data file to workaround + # gcov <= 3.2.x bug (Abort) + system_no_output(3, "mv", "$da_filename", "$da_filename.ori") + and die ("ERROR: cannot rename $da_filename\n"); + } + + # Execute gcov command and suppress standard output + $gcov_error = system_no_output(1, $gcov_tool, $da_filename, + "-o", $object_dir, @gcov_options); + + if ($da_renamed) + { + system_no_output(3, "mv", "$da_filename.ori", "$da_filename") + and die ("ERROR: cannot rename $da_filename.ori"); + } + + # Clean up temporary links + foreach (@tmp_links) { + unlink($_); + } + + if ($gcov_error) + { + if ($ignore[$ERROR_GCOV]) + { + warn("WARNING: GCOV failed for $da_filename!\n"); + return; + } + die("ERROR: GCOV failed for $da_filename!\n"); + } + + # Collect data from resulting .gcov files and create .info file + @gcov_list = get_filenames('.', '\.gcov$'); + + # Check for files + if (!@gcov_list) + { + warn("WARNING: gcov did not create any files for ". + "$da_filename!\n"); + } + + # Check whether we're writing to a single file + if ($output_filename) + { + if ($output_filename eq "-") + { + *INFO_HANDLE = *STDOUT; + } + else + { + # Append to output file + open(INFO_HANDLE, ">>$output_filename") + or die("ERROR: cannot write to ". + "$output_filename!\n"); + } + } + else + { + # Open .info file for output + open(INFO_HANDLE, ">$da_filename.info") + or die("ERROR: cannot create $da_filename.info!\n"); + } + + # Write test name + printf(INFO_HANDLE "TN:%s\n", $test_name); + + # Traverse the list of generated .gcov files and combine them into a + # single .info file + @unprocessed = keys(%{$instr}); + foreach $gcov_file (sort(@gcov_list)) + { + my $i; + my $num; + + ($source, $object) = read_gcov_header($gcov_file); + + if (defined($source)) + { + $source = solve_relative_path($base_dir, $source); + } + + # gcov will happily create output even if there's no source code + # available - this interferes with checksum creation so we need + # to pull the emergency brake here. + if (defined($source) && ! -r $source && $checksum) + { + if ($ignore[$ERROR_SOURCE]) + { + warn("WARNING: could not read source file ". + "$source\n"); + next; + } + die("ERROR: could not read source file $source\n"); + } + + @matches = match_filename(defined($source) ? $source : + $gcov_file, keys(%{$instr})); + + # Skip files that are not mentioned in the graph file + if (!@matches) + { + warn("WARNING: cannot find an entry for ".$gcov_file. + " in $graph_file_extension file, skipping ". + "file!\n"); + unlink($gcov_file); + next; + } + + # Read in contents of gcov file + @result = read_gcov_file($gcov_file); + if (!defined($result[0])) { + warn("WARNING: skipping unreadable file ". + $gcov_file."\n"); + unlink($gcov_file); + next; + } + @gcov_content = @{$result[0]}; + $gcov_branches = $result[1]; + @gcov_functions = @{$result[2]}; + + # Skip empty files + if (!@gcov_content) + { + warn("WARNING: skipping empty file ".$gcov_file."\n"); + unlink($gcov_file); + next; + } + + if (scalar(@matches) == 1) + { + # Just one match + $source_filename = $matches[0]; + } + else + { + # Try to solve the ambiguity + $source_filename = solve_ambiguous_match($gcov_file, + \@matches, \@gcov_content); + } + + # Remove processed file from list + for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--) + { + if ($unprocessed[$index] eq $source_filename) + { + splice(@unprocessed, $index, 1); + last; + } + } + + # Write absolute path of source file + printf(INFO_HANDLE "SF:%s\n", $source_filename); + + # If requested, derive function coverage data from + # line coverage data of the first line of a function + if ($opt_derive_func_data) { + @gcov_functions = + derive_data(\@gcov_content, \@gcov_functions, + $graph->{$source_filename}); + } + + # Write function-related information + if (defined($graph->{$source_filename})) + { + my $fn_data = $graph->{$source_filename}; + my $fn; + + foreach $fn (sort + {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]} + keys(%{$fn_data})) { + my $ln_data = $fn_data->{$fn}; + my $line = $ln_data->[0]; + + # Skip empty function + if ($fn eq "") { + next; + } + # Remove excluded functions + if (!$no_markers) { + my $gfn; + my $found = 0; + + foreach $gfn (@gcov_functions) { + if ($gfn eq $fn) { + $found = 1; + last; + } + } + if (!$found) { + next; + } + } + + # Normalize function name + $fn = filter_fn_name($fn); + + print(INFO_HANDLE "FN:$line,$fn\n"); + } + } + + #-- + #-- FNDA: <call-count>, <function-name> + #-- FNF: overall count of functions + #-- FNH: overall count of functions with non-zero call count + #-- + $funcs_found = 0; + $funcs_hit = 0; + while (@gcov_functions) + { + my $count = shift(@gcov_functions); + my $fn = shift(@gcov_functions); + + $fn = filter_fn_name($fn); + printf(INFO_HANDLE "FNDA:$count,$fn\n"); + $funcs_found++; + $funcs_hit++ if ($count > 0); + } + if ($funcs_found > 0) { + printf(INFO_HANDLE "FNF:%s\n", $funcs_found); + printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); + } + + # Write coverage information for each instrumented branch: + # + # BRDA:<line number>,<block number>,<branch number>,<taken> + # + # where 'taken' is the number of times the branch was taken + # or '-' if the block to which the branch belongs was never + # executed + $br_found = 0; + $br_hit = 0; + $num = br_gvec_len($gcov_branches); + for ($i = 0; $i < $num; $i++) { + my ($line, $block, $branch, $taken) = + br_gvec_get($gcov_branches, $i); + + print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); + $br_found++; + $br_hit++ if ($taken ne '-' && $taken > 0); + } + if ($br_found > 0) { + printf(INFO_HANDLE "BRF:%s\n", $br_found); + printf(INFO_HANDLE "BRH:%s\n", $br_hit); + } + + # Reset line counters + $line_number = 0; + $lines_found = 0; + $lines_hit = 0; + + # Write coverage information for each instrumented line + # Note: @gcov_content contains a list of (flag, count, source) + # tuple for each source code line + while (@gcov_content) + { + $line_number++; + + # Check for instrumented line + if ($gcov_content[0]) + { + $lines_found++; + printf(INFO_HANDLE "DA:".$line_number.",". + $gcov_content[1].($checksum ? + ",". md5_base64($gcov_content[2]) : ""). + "\n"); + + # Increase $lines_hit in case of an execution + # count>0 + if ($gcov_content[1] > 0) { $lines_hit++; } + } + + # Remove already processed data from array + splice(@gcov_content,0,3); + } + + # Write line statistics and section separator + printf(INFO_HANDLE "LF:%s\n", $lines_found); + printf(INFO_HANDLE "LH:%s\n", $lines_hit); + print(INFO_HANDLE "end_of_record\n"); + + # Remove .gcov file after processing + unlink($gcov_file); + } + + # Check for files which show up in the graph file but were never + # processed + if (@unprocessed && @gcov_list) + { + foreach (@unprocessed) + { + warn("WARNING: no data found for $_\n"); + } + } + + if (!($output_filename && ($output_filename eq "-"))) + { + close(INFO_HANDLE); + } + + # Change back to initial directory + chdir($cwd); +} + + +# +# solve_relative_path(path, dir) +# +# Solve relative path components of DIR which, if not absolute, resides in PATH. +# + +sub solve_relative_path($$) +{ + my $path = $_[0]; + my $dir = $_[1]; + my $result; + + $result = $dir; + # Prepend path if not absolute + if ($dir =~ /^[^\/]/) + { + $result = "$path/$result"; + } + + # Remove // + $result =~ s/\/\//\//g; + + # Remove . + $result =~ s/\/\.\//\//g; + + # Solve .. + while ($result =~ s/\/[^\/]+\/\.\.\//\//) + { + } + + # Remove preceding .. + $result =~ s/^\/\.\.\//\//g; + + return $result; +} + + +# +# match_filename(gcov_filename, list) +# +# Return a list of those entries of LIST which match the relative filename +# GCOV_FILENAME. +# + +sub match_filename($@) +{ + my ($filename, @list) = @_; + my ($vol, $dir, $file) = splitpath($filename); + my @comp = splitdir($dir); + my $comps = scalar(@comp); + my $entry; + my @result; + +entry: + foreach $entry (@list) { + my ($evol, $edir, $efile) = splitpath($entry); + my @ecomp; + my $ecomps; + my $i; + + # Filename component must match + if ($efile ne $file) { + next; + } + # Check directory components last to first for match + @ecomp = splitdir($edir); + $ecomps = scalar(@ecomp); + if ($ecomps < $comps) { + next; + } + for ($i = 0; $i < $comps; $i++) { + if ($comp[$comps - $i - 1] ne + $ecomp[$ecomps - $i - 1]) { + next entry; + } + } + push(@result, $entry), + } + + return @result; +} + +# +# solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) +# +# Try to solve ambiguous matches of mapping (gcov file) -> (source code) file +# by comparing source code provided in the GCOV file with that of the files +# in MATCHES. REL_FILENAME identifies the relative filename of the gcov +# file. +# +# Return the one real match or die if there is none. +# + +sub solve_ambiguous_match($$$) +{ + my $rel_name = $_[0]; + my $matches = $_[1]; + my $content = $_[2]; + my $filename; + my $index; + my $no_match; + local *SOURCE; + + # Check the list of matches + foreach $filename (@$matches) + { + + # Compare file contents + open(SOURCE, $filename) + or die("ERROR: cannot read $filename!\n"); + + $no_match = 0; + for ($index = 2; <SOURCE>; $index += 3) + { + chomp; + + # Also remove CR from line-end + s/\015$//; + + if ($_ ne @$content[$index]) + { + $no_match = 1; + last; + } + } + + close(SOURCE); + + if (!$no_match) + { + info("Solved source file ambiguity for $rel_name\n"); + return $filename; + } + } + + die("ERROR: could not match gcov data for $rel_name!\n"); +} + + +# +# split_filename(filename) +# +# Return (path, filename, extension) for a given FILENAME. +# + +sub split_filename($) +{ + my @path_components = split('/', $_[0]); + my @file_components = split('\.', pop(@path_components)); + my $extension = pop(@file_components); + + return (join("/",@path_components), join(".",@file_components), + $extension); +} + + +# +# read_gcov_header(gcov_filename) +# +# Parse file GCOV_FILENAME and return a list containing the following +# information: +# +# (source, object) +# +# where: +# +# source: complete relative path of the source code file (gcc >= 3.3 only) +# object: name of associated graph file +# +# Die on error. +# + +sub read_gcov_header($) +{ + my $source; + my $object; + local *INPUT; + + if (!open(INPUT, $_[0])) + { + if ($ignore_errors[$ERROR_GCOV]) + { + warn("WARNING: cannot read $_[0]!\n"); + return (undef,undef); + } + die("ERROR: cannot read $_[0]!\n"); + } + + while (<INPUT>) + { + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + if (/^\s+-:\s+0:Source:(.*)$/) + { + # Source: header entry + $source = $1; + } + elsif (/^\s+-:\s+0:Object:(.*)$/) + { + # Object: header entry + $object = $1; + } + else + { + last; + } + } + + close(INPUT); + + return ($source, $object); +} + + +# +# br_gvec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_gvec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_gvec_get(vector, number) +# +# Return an entry from the branch coverage vector. +# + +sub br_gvec_get($$) +{ + my ($vec, $num) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + if ($taken == 0) { + $taken = "-"; + } else { + $taken--; + } + + return ($line, $block, $branch, $taken); +} + + +# +# br_gvec_push(vector, line, block, branch, taken) +# +# Add an entry to the branch coverage vector. +# + +sub br_gvec_push($$$$$) +{ + my ($vec, $line, $block, $branch, $taken) = @_; + my $offset; + + $vec = "" if (!defined($vec)); + $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; + + # Encode taken value into an integer + if ($taken eq "-") { + $taken = 0; + } else { + $taken++; + } + + # Add to vector + vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line; + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; +} + + +# +# read_gcov_file(gcov_filename) +# +# Parse file GCOV_FILENAME (.gcov file format) and return the list: +# (reference to gcov_content, reference to gcov_branch, reference to gcov_func) +# +# gcov_content is a list of 3 elements +# (flag, count, source) for each source code line: +# +# $result[($line_number-1)*3+0] = instrumentation flag for line $line_number +# $result[($line_number-1)*3+1] = execution count for line $line_number +# $result[($line_number-1)*3+2] = source code text for line $line_number +# +# gcov_branch is a vector of 4 4-byte long elements for each branch: +# line number, block number, branch number, count + 1 or 0 +# +# gcov_func is a list of 2 elements +# (number of calls, function name) for each function +# +# Die on error. +# + +sub read_gcov_file($) +{ + my $filename = $_[0]; + my @result = (); + my $branches = ""; + my @functions = (); + my $number; + my $exclude_flag = 0; + my $exclude_line = 0; + my $last_block = $UNNAMED_BLOCK; + my $last_line = 0; + local *INPUT; + + if (!open(INPUT, $filename)) { + if ($ignore_errors[$ERROR_GCOV]) + { + warn("WARNING: cannot read $filename!\n"); + return (undef, undef, undef); + } + die("ERROR: cannot read $filename!\n"); + } + + if ($gcov_version < $GCOV_VERSION_3_3_0) + { + # Expect gcov format as used in gcc < 3.3 + while (<INPUT>) + { + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, $2); + } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, '-'); + } + elsif (/^call/ || /^function/) + { + # Function call return data + } + else + { + $last_line++; + # Check for exclusion markers + if (!$no_markers) { + if (/$EXCL_STOP/) { + $exclude_flag = 0; + } elsif (/$EXCL_START/) { + $exclude_flag = 1; + } + if (/$EXCL_LINE/ || $exclude_flag) { + $exclude_line = 1; + } else { + $exclude_line = 0; + } + } + # Source code execution data + if (/^\t\t(.*)$/) + { + # Uninstrumented line + push(@result, 0); + push(@result, 0); + push(@result, $1); + next; + } + $number = (split(" ",substr($_, 0, 16)))[0]; + + # Check for zero count which is indicated + # by ###### + if ($number eq "######") { $number = 0; } + + if ($exclude_line) { + # Register uninstrumented line instead + push(@result, 0); + push(@result, 0); + } else { + push(@result, 1); + push(@result, $number); + } + push(@result, substr($_, 16)); + } + } + } + else + { + # Expect gcov format as used in gcc >= 3.3 + while (<INPUT>) + { + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) { + # Block information - used to group related + # branches + $last_line = $2; + $last_block = $3; + } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, $2); + } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, '-'); + } + elsif (/^function\s+(\S+)\s+called\s+(\d+)/) + { + if ($exclude_line) { + next; + } + push(@functions, $2, $1); + } + elsif (/^call/) + { + # Function call return data + } + elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) + { + my ($count, $line, $code) = ($1, $2, $3); + + $last_line = $line; + $last_block = $UNNAMED_BLOCK; + # Check for exclusion markers + if (!$no_markers) { + if (/$EXCL_STOP/) { + $exclude_flag = 0; + } elsif (/$EXCL_START/) { + $exclude_flag = 1; + } + if (/$EXCL_LINE/ || $exclude_flag) { + $exclude_line = 1; + } else { + $exclude_line = 0; + } + } + # <exec count>:<line number>:<source code> + if ($line eq "0") + { + # Extra data + } + elsif ($count eq "-") + { + # Uninstrumented line + push(@result, 0); + push(@result, 0); + push(@result, $code); + } + else + { + if ($exclude_line) { + push(@result, 0); + push(@result, 0); + } else { + # Check for zero count + if ($count eq "#####") { + $count = 0; + } + push(@result, 1); + push(@result, $count); + } + push(@result, $code); + } + } + } + } + + close(INPUT); + if ($exclude_flag) { + warn("WARNING: unterminated exclusion section in $filename\n"); + } + return(\@result, $branches, \@functions); +} + + +# +# Get the GCOV tool version. Return an integer number which represents the +# GCOV version. Version numbers can be compared using standard integer +# operations. +# + +sub get_gcov_version() +{ + local *HANDLE; + my $version_string; + my $result; + + open(GCOV_PIPE, "$gcov_tool -v |") + or die("ERROR: cannot retrieve gcov version!\n"); + $version_string = <GCOV_PIPE>; + close(GCOV_PIPE); + + $result = 0; + if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) + { + if (defined($4)) + { + info("Found gcov version: $1.$2.$4\n"); + $result = $1 << 16 | $2 << 8 | $4; + } + else + { + info("Found gcov version: $1.$2\n"); + $result = $1 << 16 | $2 << 8; + } + } + if ($version_string =~ /suse/i && $result == 0x30303 || + $version_string =~ /mandrake/i && $result == 0x30302) + { + info("Using compatibility mode for GCC 3.3 (hammer)\n"); + $compatibility = $COMPAT_HAMMER; + } + return $result; +} + + +# +# info(printf_parameter) +# +# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag +# is not set. +# + +sub info(@) +{ + if (!$quiet) + { + # Print info string + if (defined($output_filename) && ($output_filename eq "-")) + { + # Don't interfere with the .info output to STDOUT + printf(STDERR @_); + } + else + { + printf(@_); + } + } +} + + +# +# int_handler() +# +# Called when the script was interrupted by an INT signal (e.g. CTRl-C) +# + +sub int_handler() +{ + if ($cwd) { chdir($cwd); } + info("Aborted.\n"); + exit(1); +} + + +# +# system_no_output(mode, parameters) +# +# Call an external program using PARAMETERS while suppressing depending on +# the value of MODE: +# +# MODE & 1: suppress STDOUT +# MODE & 2: suppress STDERR +# +# Return 0 on success, non-zero otherwise. +# + +sub system_no_output($@) +{ + my $mode = shift; + my $result; + local *OLD_STDERR; + local *OLD_STDOUT; + + # Save old stdout and stderr handles + ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); + + # Redirect to /dev/null + ($mode & 1) && open(STDOUT, ">/dev/null"); + ($mode & 2) && open(STDERR, ">/dev/null"); + + system(@_); + $result = $?; + + # Close redirected handles + ($mode & 1) && close(STDOUT); + ($mode & 2) && close(STDERR); + + # Restore old handles + ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); + + return $result; +} + + +# +# read_config(filename) +# +# Read configuration file FILENAME and return a reference to a hash containing +# all valid key=value pairs found. +# + +sub read_config($) +{ + my $filename = $_[0]; + my %result; + my $key; + my $value; + local *HANDLE; + + if (!open(HANDLE, "<$filename")) + { + warn("WARNING: cannot read configuration file $filename\n"); + return undef; + } + while (<HANDLE>) + { + chomp; + # Skip comments + s/#.*//; + # Remove leading blanks + s/^\s+//; + # Remove trailing blanks + s/\s+$//; + next unless length; + ($key, $value) = split(/\s*=\s*/, $_, 2); + if (defined($key) && defined($value)) + { + $result{$key} = $value; + } + else + { + warn("WARNING: malformed statement in line $. ". + "of configuration file $filename\n"); + } + } + close(HANDLE); + return \%result; +} + + +# +# apply_config(REF) +# +# REF is a reference to a hash containing the following mapping: +# +# key_string => var_ref +# +# where KEY_STRING is a keyword and VAR_REF is a reference to an associated +# variable. If the global configuration hash CONFIG contains a value for +# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# + +sub apply_config($) +{ + my $ref = $_[0]; + + foreach (keys(%{$ref})) + { + if (defined($config->{$_})) + { + ${$ref->{$_}} = $config->{$_}; + } + } +} + + +# +# get_exclusion_data(filename) +# +# Scan specified source code file for exclusion markers and return +# linenumber -> 1 +# for all lines which should be excluded. +# + +sub get_exclusion_data($) +{ + my ($filename) = @_; + my %list; + my $flag = 0; + local *HANDLE; + + if (!open(HANDLE, "<$filename")) { + warn("WARNING: could not open $filename\n"); + return undef; + } + while (<HANDLE>) { + if (/$EXCL_STOP/) { + $flag = 0; + } elsif (/$EXCL_START/) { + $flag = 1; + } + if (/$EXCL_LINE/ || $flag) { + $list{$.} = 1; + } + } + close(HANDLE); + + if ($flag) { + warn("WARNING: unterminated exclusion section in $filename\n"); + } + + return \%list; +} + + +# +# apply_exclusion_data(instr, graph) +# +# Remove lines from instr and graph data structures which are marked +# for exclusion in the source code file. +# +# Return adjusted (instr, graph). +# +# graph : file name -> function data +# function data : function name -> line data +# line data : [ line1, line2, ... ] +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# + +sub apply_exclusion_data($$) +{ + my ($instr, $graph) = @_; + my $filename; + my %excl_data; + my $excl_read_failed = 0; + + # Collect exclusion marker data + foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) { + my $excl = get_exclusion_data($filename); + + # Skip and note if file could not be read + if (!defined($excl)) { + $excl_read_failed = 1; + next; + } + + # Add to collection if there are markers + $excl_data{$filename} = $excl if (keys(%{$excl}) > 0); + } + + # Warn if not all source files could be read + if ($excl_read_failed) { + warn("WARNING: some exclusion markers may be ignored\n"); + } + + # Skip if no markers were found + return ($instr, $graph) if (keys(%excl_data) == 0); + + # Apply exclusion marker data to graph + foreach $filename (keys(%excl_data)) { + my $function_data = $graph->{$filename}; + my $excl = $excl_data{$filename}; + my $function; + + next if (!defined($function_data)); + + foreach $function (keys(%{$function_data})) { + my $line_data = $function_data->{$function}; + my $line; + my @new_data; + + # To be consistent with exclusion parser in non-initial + # case we need to remove a function if the first line + # was excluded + if ($excl->{$line_data->[0]}) { + delete($function_data->{$function}); + next; + } + # Copy only lines which are not excluded + foreach $line (@{$line_data}) { + push(@new_data, $line) if (!$excl->{$line}); + } + + # Store modified list + if (scalar(@new_data) > 0) { + $function_data->{$function} = \@new_data; + } else { + # All of this function was excluded + delete($function_data->{$function}); + } + } + + # Check if all functions of this file were excluded + if (keys(%{$function_data}) == 0) { + delete($graph->{$filename}); + } + } + + # Apply exclusion marker data to instr + foreach $filename (keys(%excl_data)) { + my $line_data = $instr->{$filename}; + my $excl = $excl_data{$filename}; + my $line; + my @new_data; + + next if (!defined($line_data)); + + # Copy only lines which are not excluded + foreach $line (@{$line_data}) { + push(@new_data, $line) if (!$excl->{$line}); + } + + # Store modified list + if (scalar(@new_data) > 0) { + $instr->{$filename} = \@new_data; + } else { + # All of this file was excluded + delete($instr->{$filename}); + } + } + + return ($instr, $graph); +} + + +sub process_graphfile($$) +{ + my ($file, $dir) = @_; + my $graph_filename = $file; + my $graph_dir; + my $graph_basename; + my $source_dir; + my $base_dir; + my $graph; + my $instr; + my $filename; + local *INFO_HANDLE; + + info("Processing %s\n", abs2rel($file, $dir)); + + # Get path to data file in absolute and normalized form (begins with /, + # contains no more ../ or ./) + $graph_filename = solve_relative_path($cwd, $graph_filename); + + # Get directory and basename of data file + ($graph_dir, $graph_basename) = split_filename($graph_filename); + + # avoid files from .libs dirs + if ($compat_libtool && $graph_dir =~ m/(.*)\/\.libs$/) { + $source_dir = $1; + } else { + $source_dir = $graph_dir; + } + + # Construct base_dir for current file + if ($base_directory) + { + $base_dir = $base_directory; + } + else + { + $base_dir = $source_dir; + } + + if ($gcov_version < $GCOV_VERSION_3_4_0) + { + if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + { + ($instr, $graph) = read_bbg($graph_filename, $base_dir); + } + else + { + ($instr, $graph) = read_bb($graph_filename, $base_dir); + } + } + else + { + ($instr, $graph) = read_gcno($graph_filename, $base_dir); + } + + if (!$no_markers) { + # Apply exclusion marker data to graph file data + ($instr, $graph) = apply_exclusion_data($instr, $graph); + } + + # Check whether we're writing to a single file + if ($output_filename) + { + if ($output_filename eq "-") + { + *INFO_HANDLE = *STDOUT; + } + else + { + # Append to output file + open(INFO_HANDLE, ">>$output_filename") + or die("ERROR: cannot write to ". + "$output_filename!\n"); + } + } + else + { + # Open .info file for output + open(INFO_HANDLE, ">$graph_filename.info") + or die("ERROR: cannot create $graph_filename.info!\n"); + } + + # Write test name + printf(INFO_HANDLE "TN:%s\n", $test_name); + foreach $filename (sort(keys(%{$instr}))) + { + my $funcdata = $graph->{$filename}; + my $line; + my $linedata; + + print(INFO_HANDLE "SF:$filename\n"); + + if (defined($funcdata)) { + my @functions = sort {$funcdata->{$a}->[0] <=> + $funcdata->{$b}->[0]} + keys(%{$funcdata}); + my $func; + + # Gather list of instrumented lines and functions + foreach $func (@functions) { + $linedata = $funcdata->{$func}; + + # Print function name and starting line + print(INFO_HANDLE "FN:".$linedata->[0]. + ",".filter_fn_name($func)."\n"); + } + # Print zero function coverage data + foreach $func (@functions) { + print(INFO_HANDLE "FNDA:0,". + filter_fn_name($func)."\n"); + } + # Print function summary + print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); + print(INFO_HANDLE "FNH:0\n"); + } + # Print zero line coverage data + foreach $line (@{$instr->{$filename}}) { + print(INFO_HANDLE "DA:$line,0\n"); + } + # Print line summary + print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n"); + print(INFO_HANDLE "LH:0\n"); + + print(INFO_HANDLE "end_of_record\n"); + } + if (!($output_filename && ($output_filename eq "-"))) + { + close(INFO_HANDLE); + } +} + +sub filter_fn_name($) +{ + my ($fn) = @_; + + # Remove characters used internally as function name delimiters + $fn =~ s/[,=]/_/g; + + return $fn; +} + +sub warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub die_handler($) +{ + my ($msg) = @_; + + die("$tool_name: $msg"); +} + + +# +# graph_error(filename, message) +# +# Print message about error in graph file. If ignore_graph_error is set, return. +# Otherwise abort. +# + +sub graph_error($$) +{ + my ($filename, $msg) = @_; + + if ($ignore[$ERROR_GRAPH]) { + warn("WARNING: $filename: $msg - skipping\n"); + return; + } + die("ERROR: $filename: $msg\n"); +} + +# +# graph_expect(description) +# +# If debug is set to a non-zero value, print the specified description of what +# is expected to be read next from the graph file. +# + +sub graph_expect($) +{ + my ($msg) = @_; + + if (!$debug || !defined($msg)) { + return; + } + + print(STDERR "DEBUG: expecting $msg\n"); +} + +# +# graph_read(handle, bytes[, description]) +# +# Read and return the specified number of bytes from handle. Return undef +# if the number of bytes could not be read. +# + +sub graph_read(*$;$) +{ + my ($handle, $length, $desc) = @_; + my $data; + my $result; + + graph_expect($desc); + $result = read($handle, $data, $length); + if ($debug) { + my $ascii = ""; + my $hex = ""; + my $i; + + print(STDERR "DEBUG: read($length)=$result: "); + for ($i = 0; $i < length($data); $i++) { + my $c = substr($data, $i, 1);; + my $n = ord($c); + + $hex .= sprintf("%02x ", $n); + if ($n >= 32 && $n <= 127) { + $ascii .= $c; + } else { + $ascii .= "."; + } + } + print(STDERR "$hex |$ascii|"); + print(STDERR "\n"); + } + if ($result != $length) { + return undef; + } + return $data; +} + +# +# graph_skip(handle, bytes[, description]) +# +# Read and discard the specified number of bytes from handle. Return non-zero +# if bytes could be read, zero otherwise. +# + +sub graph_skip(*$;$) +{ + my ($handle, $length, $desc) = @_; + + if (defined(graph_read($handle, $length, $desc))) { + return 1; + } + return 0; +} + +# +# sort_uniq(list) +# +# Return list in numerically ascending order and without duplicate entries. +# + +sub sort_uniq(@) +{ + my (@list) = @_; + my %hash; + + foreach (@list) { + $hash{$_} = 1; + } + return sort { $a <=> $b } keys(%hash); +} + +# +# sort_uniq_lex(list) +# +# Return list in lexically ascending order and without duplicate entries. +# + +sub sort_uniq_lex(@) +{ + my (@list) = @_; + my %hash; + + foreach (@list) { + $hash{$_} = 1; + } + return sort keys(%hash); +} + +# +# graph_cleanup(graph) +# +# Remove entries for functions with no lines. Remove duplicate line numbers. +# Sort list of line numbers numerically ascending. +# + +sub graph_cleanup($) +{ + my ($graph) = @_; + my $filename; + + foreach $filename (keys(%{$graph})) { + my $per_file = $graph->{$filename}; + my $function; + + foreach $function (keys(%{$per_file})) { + my $lines = $per_file->{$function}; + + if (scalar(@$lines) == 0) { + # Remove empty function + delete($per_file->{$function}); + next; + } + # Normalize list + $per_file->{$function} = [ sort_uniq(@$lines) ]; + } + if (scalar(keys(%{$per_file})) == 0) { + # Remove empty file + delete($graph->{$filename}); + } + } +} + +# +# graph_find_base(bb) +# +# Try to identify the filename which is the base source file for the +# specified bb data. +# + +sub graph_find_base($) +{ + my ($bb) = @_; + my %file_count; + my $basefile; + my $file; + my $func; + my $filedata; + my $count; + my $num; + + # Identify base name for this bb data. + foreach $func (keys(%{$bb})) { + $filedata = $bb->{$func}; + + foreach $file (keys(%{$filedata})) { + $count = $file_count{$file}; + + # Count file occurrence + $file_count{$file} = defined($count) ? $count + 1 : 1; + } + } + $count = 0; + $num = 0; + foreach $file (keys(%file_count)) { + if ($file_count{$file} > $count) { + # The file that contains code for the most functions + # is likely the base file + $count = $file_count{$file}; + $num = 1; + $basefile = $file; + } elsif ($file_count{$file} == $count) { + # If more than one file could be the basefile, we + # don't have a basefile + $basefile = undef; + } + } + + return $basefile; +} + +# +# graph_from_bb(bb, fileorder, bb_filename) +# +# Convert data from bb to the graph format and list of instrumented lines. +# Returns (instr, graph). +# +# bb : function name -> file data +# : undef -> file order +# file data : filename -> line data +# line data : [ line1, line2, ... ] +# +# file order : function name -> [ filename1, filename2, ... ] +# +# graph : file name -> function data +# function data : function name -> line data +# line data : [ line1, line2, ... ] +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# + +sub graph_from_bb($$$) +{ + my ($bb, $fileorder, $bb_filename) = @_; + my $graph = {}; + my $instr = {}; + my $basefile; + my $file; + my $func; + my $filedata; + my $linedata; + my $order; + + $basefile = graph_find_base($bb); + # Create graph structure + foreach $func (keys(%{$bb})) { + $filedata = $bb->{$func}; + $order = $fileorder->{$func}; + + # Account for lines in functions + if (defined($basefile) && defined($filedata->{$basefile})) { + # If the basefile contributes to this function, + # account this function to the basefile. + $graph->{$basefile}->{$func} = $filedata->{$basefile}; + } else { + # If the basefile does not contribute to this function, + # account this function to the first file contributing + # lines. + $graph->{$order->[0]}->{$func} = + $filedata->{$order->[0]}; + } + + foreach $file (keys(%{$filedata})) { + # Account for instrumented lines + $linedata = $filedata->{$file}; + push(@{$instr->{$file}}, @$linedata); + } + } + # Clean up array of instrumented lines + foreach $file (keys(%{$instr})) { + $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ]; + } + + return ($instr, $graph); +} + +# +# graph_add_order(fileorder, function, filename) +# +# Add an entry for filename to the fileorder data set for function. +# + +sub graph_add_order($$$) +{ + my ($fileorder, $function, $filename) = @_; + my $item; + my $list; + + $list = $fileorder->{$function}; + foreach $item (@$list) { + if ($item eq $filename) { + return; + } + } + push(@$list, $filename); + $fileorder->{$function} = $list; +} +# +# read_bb_word(handle[, description]) +# +# Read and return a word in .bb format from handle. +# + +sub read_bb_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_bb_value(handle[, description]) +# +# Read a word in .bb format from handle and return the word and its integer +# value. +# + +sub read_bb_value(*;$) +{ + my ($handle, $desc) = @_; + my $word; + + $word = read_bb_word($handle, $desc); + return undef if (!defined($word)); + + return ($word, unpack("V", $word)); +} + +# +# read_bb_string(handle, delimiter) +# +# Read and return a string in .bb format from handle up to the specified +# delimiter value. +# + +sub read_bb_string(*$) +{ + my ($handle, $delimiter) = @_; + my $word; + my $value; + my $string = ""; + + graph_expect("string"); + do { + ($word, $value) = read_bb_value($handle, "string or delimiter"); + return undef if (!defined($value)); + if ($value != $delimiter) { + $string .= $word; + } + } while ($value != $delimiter); + $string =~ s/\0//g; + + return $string; +} + +# +# read_bb(filename, base_dir) +# +# Read the contents of the specified .bb file and return (instr, graph), where: +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# +# graph : filename -> file_data +# file_data : function name -> line_data +# line_data : [ line1, line2, ... ] +# +# Relative filenames are converted to absolute form using base_dir as +# base directory. See the gcov info pages of gcc 2.95 for a description of +# the .bb file format. +# + +sub read_bb($$) +{ + my ($bb_filename, $base) = @_; + my $minus_one = 0x80000001; + my $minus_two = 0x80000002; + my $value; + my $filename; + my $function; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<$bb_filename") or goto open_error; + binmode(HANDLE); + while (!eof(HANDLE)) { + $value = read_bb_value(*HANDLE, "data word"); + goto incomplete if (!defined($value)); + if ($value == $minus_one) { + # Source file name + graph_expect("filename"); + $filename = read_bb_string(*HANDLE, $minus_one); + goto incomplete if (!defined($filename)); + if ($filename ne "") { + $filename = solve_relative_path($base, + $filename); + } + } elsif ($value == $minus_two) { + # Function name + graph_expect("function name"); + $function = read_bb_string(*HANDLE, $minus_two); + goto incomplete if (!defined($function)); + } elsif ($value > 0) { + # Line number + if (!defined($filename) || !defined($function)) { + warn("WARNING: unassigned line number ". + "$value\n"); + next; + } + push(@{$bb->{$function}->{$filename}}, $value); + graph_add_order($fileorder, $function, $filename); + } + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($bb_filename, "could not open file"); + return undef; +incomplete: + graph_error($bb_filename, "reached unexpected end of file"); + return undef; +} + +# +# read_bbg_word(handle[, description]) +# +# Read and return a word in .bbg format. +# + +sub read_bbg_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_bbg_value(handle[, description]) +# +# Read a word in .bbg format from handle and return its integer value. +# + +sub read_bbg_value(*;$) +{ + my ($handle, $desc) = @_; + my $word; + + $word = read_bbg_word($handle, $desc); + return undef if (!defined($word)); + + return unpack("N", $word); +} + +# +# read_bbg_string(handle) +# +# Read and return a string in .bbg format. +# + +sub read_bbg_string(*) +{ + my ($handle, $desc) = @_; + my $length; + my $string; + + graph_expect("string"); + # Read string length + $length = read_bbg_value($handle, "string length"); + return undef if (!defined($length)); + if ($length == 0) { + return ""; + } + # Read string + $string = graph_read($handle, $length, "string"); + return undef if (!defined($string)); + # Skip padding + graph_skip($handle, 4 - $length % 4, "string padding") or return undef; + + return $string; +} + +# +# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, +# function, base) +# +# Read a bbg format lines record from handle and add the relevant data to +# bb and fileorder. Return filename on success, undef on error. +# + +sub read_bbg_lines_record(*$$$$$$) +{ + my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function, + $base) = @_; + my $string; + my $lineno; + + graph_expect("lines record"); + # Skip basic block index + graph_skip($handle, 4, "basic block index") or return undef; + while (1) { + # Read line number + $lineno = read_bbg_value($handle, "line number"); + return undef if (!defined($lineno)); + if ($lineno == 0) { + # Got a marker for a new filename + graph_expect("filename"); + $string = read_bbg_string($handle); + return undef if (!defined($string)); + # Check for end of record + if ($string eq "") { + return $filename; + } + $filename = solve_relative_path($base, $string); + next; + } + # Got an actual line number + if (!defined($filename)) { + warn("WARNING: unassigned line number in ". + "$bbg_filename\n"); + next; + } + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + } +} + +# +# read_bbg(filename, base_dir) +# +# Read the contents of the specified .bbg file and return the following mapping: +# graph: filename -> file_data +# file_data: function name -> line_data +# line_data: [ line1, line2, ... ] +# +# Relative filenames are converted to absolute form using base_dir as +# base directory. See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code +# for a description of the .bbg format. +# + +sub read_bbg($$) +{ + my ($bbg_filename, $base) = @_; + my $file_magic = 0x67626267; + my $tag_function = 0x01000000; + my $tag_lines = 0x01450000; + my $word; + my $tag; + my $length; + my $function; + my $filename; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<$bbg_filename") or goto open_error; + binmode(HANDLE); + # Read magic + $word = read_bbg_value(*HANDLE, "file magic"); + goto incomplete if (!defined($word)); + # Check magic + if ($word != $file_magic) { + goto magic_error; + } + # Skip version + graph_skip(*HANDLE, 4, "version") or goto incomplete; + while (!eof(HANDLE)) { + # Read record tag + $tag = read_bbg_value(*HANDLE, "record tag"); + goto incomplete if (!defined($tag)); + # Read record length + $length = read_bbg_value(*HANDLE, "record length"); + goto incomplete if (!defined($tag)); + if ($tag == $tag_function) { + graph_expect("function record"); + # Read function name + graph_expect("function name"); + $function = read_bbg_string(*HANDLE); + goto incomplete if (!defined($function)); + $filename = undef; + # Skip function checksum + graph_skip(*HANDLE, 4, "function checksum") + or goto incomplete; + } elsif ($tag == $tag_lines) { + # Read lines record + $filename = read_bbg_lines_record(HANDLE, $bbg_filename, + $bb, $fileorder, $filename, + $function, $base); + goto incomplete if (!defined($filename)); + } else { + # Skip record contents + graph_skip(*HANDLE, $length, "unhandled record") + or goto incomplete; + } + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($bbg_filename, "could not open file"); + return undef; +incomplete: + graph_error($bbg_filename, "reached unexpected end of file"); + return undef; +magic_error: + graph_error($bbg_filename, "found unrecognized bbg file magic"); + return undef; +} + +# +# read_gcno_word(handle[, description]) +# +# Read and return a word in .gcno format. +# + +sub read_gcno_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_gcno_value(handle, big_endian[, description]) +# +# Read a word in .gcno format from handle and return its integer value +# according to the specified endianness. +# + +sub read_gcno_value(*$;$) +{ + my ($handle, $big_endian, $desc) = @_; + my $word; + + $word = read_gcno_word($handle, $desc); + return undef if (!defined($word)); + if ($big_endian) { + return unpack("N", $word); + } else { + return unpack("V", $word); + } +} + +# +# read_gcno_string(handle, big_endian) +# +# Read and return a string in .gcno format. +# + +sub read_gcno_string(*$) +{ + my ($handle, $big_endian) = @_; + my $length; + my $string; + + graph_expect("string"); + # Read string length + $length = read_gcno_value($handle, $big_endian, "string length"); + return undef if (!defined($length)); + if ($length == 0) { + return ""; + } + $length *= 4; + # Read string + $string = graph_read($handle, $length, "string and padding"); + return undef if (!defined($string)); + $string =~ s/\0//g; + + return $string; +} + +# +# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, +# function, base, big_endian) +# +# Read a gcno format lines record from handle and add the relevant data to +# bb and fileorder. Return filename on success, undef on error. +# + +sub read_gcno_lines_record(*$$$$$$$) +{ + my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, + $base, $big_endian) = @_; + my $string; + my $lineno; + + graph_expect("lines record"); + # Skip basic block index + graph_skip($handle, 4, "basic block index") or return undef; + while (1) { + # Read line number + $lineno = read_gcno_value($handle, $big_endian, "line number"); + return undef if (!defined($lineno)); + if ($lineno == 0) { + # Got a marker for a new filename + graph_expect("filename"); + $string = read_gcno_string($handle, $big_endian); + return undef if (!defined($string)); + # Check for end of record + if ($string eq "") { + return $filename; + } + $filename = solve_relative_path($base, $string); + next; + } + # Got an actual line number + if (!defined($filename)) { + warn("WARNING: unassigned line number in ". + "$gcno_filename\n"); + next; + } + # Add to list + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + } +} + +# +# read_gcno_function_record(handle, graph, base, big_endian) +# +# Read a gcno format function record from handle and add the relevant data +# to graph. Return (filename, function) on success, undef on error. +# + +sub read_gcno_function_record(*$$$$) +{ + my ($handle, $bb, $fileorder, $base, $big_endian) = @_; + my $filename; + my $function; + my $lineno; + my $lines; + + graph_expect("function record"); + # Skip ident and checksum + graph_skip($handle, 8, "function ident and checksum") or return undef; + # Read function name + graph_expect("function name"); + $function = read_gcno_string($handle, $big_endian); + return undef if (!defined($function)); + # Read filename + graph_expect("filename"); + $filename = read_gcno_string($handle, $big_endian); + return undef if (!defined($filename)); + $filename = solve_relative_path($base, $filename); + # Read first line number + $lineno = read_gcno_value($handle, $big_endian, "initial line number"); + return undef if (!defined($lineno)); + # Add to list + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + + return ($filename, $function); +} + +# +# read_gcno(filename, base_dir) +# +# Read the contents of the specified .gcno file and return the following +# mapping: +# graph: filename -> file_data +# file_data: function name -> line_data +# line_data: [ line1, line2, ... ] +# +# Relative filenames are converted to absolute form using base_dir as +# base directory. See the gcov-io.h file in the gcc 3.3 source code +# for a description of the .gcno format. +# + +sub read_gcno($$) +{ + my ($gcno_filename, $base) = @_; + my $file_magic = 0x67636e6f; + my $tag_function = 0x01000000; + my $tag_lines = 0x01450000; + my $big_endian; + my $word; + my $tag; + my $length; + my $filename; + my $function; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<$gcno_filename") or goto open_error; + binmode(HANDLE); + # Read magic + $word = read_gcno_word(*HANDLE, "file magic"); + goto incomplete if (!defined($word)); + # Determine file endianness + if (unpack("N", $word) == $file_magic) { + $big_endian = 1; + } elsif (unpack("V", $word) == $file_magic) { + $big_endian = 0; + } else { + goto magic_error; + } + # Skip version and stamp + graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete; + while (!eof(HANDLE)) { + my $next_pos; + my $curr_pos; + + # Read record tag + $tag = read_gcno_value(*HANDLE, $big_endian, "record tag"); + goto incomplete if (!defined($tag)); + # Read record length + $length = read_gcno_value(*HANDLE, $big_endian, + "record length"); + goto incomplete if (!defined($length)); + # Convert length to bytes + $length *= 4; + # Calculate start of next record + $next_pos = tell(HANDLE); + goto tell_error if ($next_pos == -1); + $next_pos += $length; + # Process record + if ($tag == $tag_function) { + ($filename, $function) = read_gcno_function_record( + *HANDLE, $bb, $fileorder, $base, $big_endian); + goto incomplete if (!defined($function)); + } elsif ($tag == $tag_lines) { + # Read lines record + $filename = read_gcno_lines_record(*HANDLE, + $gcno_filename, $bb, $fileorder, + $filename, $function, $base, + $big_endian); + goto incomplete if (!defined($filename)); + } else { + # Skip record contents + graph_skip(*HANDLE, $length, "unhandled record") + or goto incomplete; + } + # Ensure that we are at the start of the next record + $curr_pos = tell(HANDLE); + goto tell_error if ($curr_pos == -1); + next if ($curr_pos == $next_pos); + goto record_error if ($curr_pos > $next_pos); + graph_skip(*HANDLE, $next_pos - $curr_pos, + "unhandled record content") + or goto incomplete; + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($gcno_filename, "could not open file"); + return undef; +incomplete: + graph_error($gcno_filename, "reached unexpected end of file"); + return undef; +magic_error: + graph_error($gcno_filename, "found unrecognized gcno file magic"); + return undef; +tell_error: + graph_error($gcno_filename, "could not determine file position"); + return undef; +record_error: + graph_error($gcno_filename, "found unrecognized record format"); + return undef; +} + +sub debug($) +{ + my ($msg) = @_; + + return if (!$debug); + print(STDERR "DEBUG: $msg"); +} + +# +# get_gcov_capabilities +# +# Determine the list of available gcov options. +# + +sub get_gcov_capabilities() +{ + my $help = `$gcov_tool --help`; + my %capabilities; + + foreach (split(/\n/, $help)) { + next if (!/--(\S+)/); + next if ($1 eq 'help'); + next if ($1 eq 'version'); + next if ($1 eq 'object-directory'); + + $capabilities{$1} = 1; + debug("gcov has capability '$1'\n"); + } + + return \%capabilities; +} |