summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '3rdParty/LCov/geninfo')
-rwxr-xr-x3rdParty/LCov/geninfo2488
1 files changed, 1689 insertions, 799 deletions
diff --git a/3rdParty/LCov/geninfo b/3rdParty/LCov/geninfo
index 055641b..dcb1a67 100755
--- a/3rdParty/LCov/geninfo
+++ b/3rdParty/LCov/geninfo
@@ -1,6 +1,6 @@
#!/usr/bin/perl -w
#
-# Copyright (c) International Business Machines Corp., 2002,2007
+# 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
@@ -51,12 +51,14 @@
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.7";
+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);
@@ -72,34 +74,75 @@ 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 process_dafile($$);
sub match_filename($@);
sub solve_ambiguous_match($$$);
sub split_filename($);
sub solve_relative_path($$);
-sub get_dir($);
sub read_gcov_header($);
sub read_gcov_file($);
-sub read_bb_file($$);
-sub read_string(*$);
-sub read_gcno_file($$);
-sub read_gcno_string(*$);
-sub read_hammer_bbg_file($$);
-sub read_hammer_bbg_string(*$);
-sub unpack_int32($$);
sub info(@);
sub get_gcov_version();
sub system_no_output($@);
sub read_config($);
sub apply_config($);
-sub gen_initial_info($);
-sub process_graphfile($);
+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;
@@ -115,7 +158,6 @@ our $version;
our $follow;
our $checksum;
our $no_checksum;
-our $preserve_paths;
our $compat_libtool;
our $no_compat_libtool;
our $adjust_testname;
@@ -127,6 +169,11 @@ 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);
@@ -141,8 +188,14 @@ $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 (-r $ENV{"HOME"}."/.lcovrc")
+if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
{
$config = read_config($ENV{"HOME"}."/.lcovrc");
}
@@ -170,21 +223,24 @@ if ($config)
}
# Parse command line options
-if (!GetOptions("test-name=s" => \$test_name,
- "output-filename=s" => \$output_filename,
+if (!GetOptions("test-name|t=s" => \$test_name,
+ "output-filename|o=s" => \$output_filename,
"checksum" => \$checksum,
"no-checksum" => \$no_checksum,
- "base-directory=s" => \$base_directory,
- "version" =>\$version,
- "quiet" => \$quiet,
- "help|?" => \$help,
- "follow" => \$follow,
+ "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");
@@ -323,6 +379,7 @@ if (@ignore_errors)
{
/^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");
}
}
@@ -353,11 +410,12 @@ else
$graph_file_extension = ".gcno";
}
-# Check for availability of --preserve-paths option of gcov
-if (`$gcov_tool --help` =~ /--preserve-paths/)
-{
- $preserve_paths = "--preserve-paths";
-}
+# 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 "-"))
@@ -378,19 +436,13 @@ if (defined($output_filename) && ($output_filename ne "-"))
}
# Do something
-if ($initial)
-{
- foreach (@data_directory)
- {
- gen_initial_info($_);
- }
+foreach my $entry (@data_directory) {
+ gen_info($entry);
}
-else
-{
- foreach (@data_directory)
- {
- gen_info($_);
- }
+
+if ($initial) {
+ warn("Note: --initial does not generate branch coverage ".
+ "data\n");
}
info("Finished .info-file creation\n");
@@ -426,15 +478,51 @@ sequentially.
--(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)
- --no-recursion Exlude subdirectories from processing
+ --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)
@@ -473,52 +561,166 @@ 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 $data_file_extension ".
- "files ...\n");
+ info("Scanning $directory for $ext files ...\n");
- @file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -type f 2>/dev/null`;
+ @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`;
chomp(@file_list);
- @file_list or die("ERROR: no $data_file_extension files found ".
- "in $directory!\n");
- info("Found %d data files in %s\n", $#file_list+1, $directory);
+ @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_list) { process_dafile($_); }
+ 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;
+}
+
#
-# process_dafile(da_filename)
+# get_filenames(directory, pattern)
#
-# Create a .info file for a single data file.
+# Return a list of filenames found in directory which match the specified
+# pattern.
#
# Die on error.
#
-sub process_dafile($)
+sub get_filenames($$)
{
- info("Processing %s\n", $_[0]);
+ 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_content; # Contents of 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_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
@@ -526,19 +728,23 @@ sub process_dafile($)
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, $_[0]);
+ $da_filename = solve_relative_path($cwd, $file);
# Get directory and basename of data file
($da_dir, $da_basename) = split_filename($da_filename);
@@ -577,7 +783,8 @@ sub process_dafile($)
}
# Construct name of graph file
- $bb_filename = $da_dir."/".$da_basename.$graph_file_extension;
+ $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
@@ -603,17 +810,16 @@ sub process_dafile($)
{
if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
{
- %bb_content = read_hammer_bbg_file($bb_filename,
- $base_dir);
+ ($instr, $graph) = read_bbg($bb_filename, $base_dir);
}
else
{
- %bb_content = read_bb_file($bb_filename, $base_dir);
+ ($instr, $graph) = read_bb($bb_filename, $base_dir);
}
}
else
{
- %bb_content = read_gcno_file($bb_filename, $base_dir);
+ ($instr, $graph) = read_gcno($bb_filename, $base_dir);
}
# Set $object_dir to real location of object files. This may differ
@@ -630,6 +836,17 @@ sub process_dafile($)
"$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
@@ -644,19 +861,8 @@ sub process_dafile($)
}
# Execute gcov command and suppress standard output
- if ($preserve_paths)
- {
- $gcov_error = system_no_output(1, $gcov_tool, $da_filename,
- "-o", $object_dir,
- "--preserve-paths",
- "-b");
- }
- else
- {
- $gcov_error = system_no_output(1, $gcov_tool, $da_filename,
- "-o", $object_dir,
- "-b");
- }
+ $gcov_error = system_no_output(1, $gcov_tool, $da_filename,
+ "-o", $object_dir, @gcov_options);
if ($da_renamed)
{
@@ -664,10 +870,9 @@ sub process_dafile($)
and die ("ERROR: cannot rename $da_filename.ori");
}
- # Clean up link
- if ($object_dir ne $da_dir)
- {
- unlink($object_dir."/".$da_basename.$data_file_extension);
+ # Clean up temporary links
+ foreach (@tmp_links) {
+ unlink($_);
}
if ($gcov_error)
@@ -681,7 +886,7 @@ sub process_dafile($)
}
# Collect data from resulting .gcov files and create .info file
- @gcov_list = glob("*.gcov");
+ @gcov_list = get_filenames('.', '\.gcov$');
# Check for files
if (!@gcov_list)
@@ -717,9 +922,12 @@ sub process_dafile($)
# Traverse the list of generated .gcov files and combine them into a
# single .info file
- @unprocessed = keys(%bb_content);
- foreach $gcov_file (@gcov_list)
+ @unprocessed = keys(%{$instr});
+ foreach $gcov_file (sort(@gcov_list))
{
+ my $i;
+ my $num;
+
($source, $object) = read_gcov_header($gcov_file);
if (defined($source))
@@ -742,7 +950,7 @@ sub process_dafile($)
}
@matches = match_filename(defined($source) ? $source :
- $gcov_file, keys(%bb_content));
+ $gcov_file, keys(%{$instr}));
# Skip files that are not mentioned in the graph file
if (!@matches)
@@ -756,8 +964,14 @@ sub process_dafile($)
# 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_branches = $result[1];
@gcov_functions = @{$result[2]};
# Skip empty files
@@ -793,19 +1007,48 @@ sub process_dafile($)
# 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($bb_content{$source_filename}))
+ if (defined($graph->{$source_filename}))
{
- foreach (split(",",$bb_content{$source_filename}))
- {
- my ($fn, $line) = split("=", $_);
+ 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 =~ s/\W/_/g;
+ $fn = filter_fn_name($fn);
print(INFO_HANDLE "FN:$line,$fn\n");
}
@@ -820,18 +1063,42 @@ sub process_dafile($)
$funcs_hit = 0;
while (@gcov_functions)
{
- printf(INFO_HANDLE "FNDA:%s,%s\n",
- $gcov_functions[0],
- $gcov_functions[1]);
- $funcs_found++;
- $funcs_hit++ if $gcov_functions[0];
- splice(@gcov_functions,0,2);
+ 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;
@@ -862,27 +1129,6 @@ sub process_dafile($)
splice(@gcov_content,0,3);
}
- #--
- #-- BA: <code-line>, <branch-coverage>
- #--
- #-- print one BA line for every branch of a
- #-- conditional. <branch-coverage> values
- #-- are:
- #-- 0 - not executed
- #-- 1 - executed but not taken
- #-- 2 - executed and taken
- #--
- while (@gcov_branches)
- {
- if ($gcov_branches[0])
- {
- printf(INFO_HANDLE "BA:%s,%s\n",
- $gcov_branches[0],
- $gcov_branches[1]);
- }
- splice(@gcov_branches,0,2);
- }
-
# Write line statistics and section separator
printf(INFO_HANDLE "LF:%s\n", $lines_found);
printf(INFO_HANDLE "LH:%s\n", $lines_hit);
@@ -958,28 +1204,42 @@ sub solve_relative_path($$)
sub match_filename($@)
{
- my $filename = shift;
- my @list = @_;
+ my ($filename, @list) = @_;
+ my ($vol, $dir, $file) = splitpath($filename);
+ my @comp = splitdir($dir);
+ my $comps = scalar(@comp);
+ my $entry;
my @result;
- $filename =~ s/^(.*).gcov$/$1/;
-
- if ($filename =~ /^\/(.*)$/)
- {
- $filename = "$1";
- }
+entry:
+ foreach $entry (@list) {
+ my ($evol, $edir, $efile) = splitpath($entry);
+ my @ecomp;
+ my $ecomps;
+ my $i;
- foreach (@list)
- {
- if (/\/\Q$filename\E(.*)$/ && $1 eq "")
- {
- @result = (@result, $_);
+ # 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)
#
@@ -1014,6 +1274,9 @@ sub solve_ambiguous_match($$$)
{
chomp;
+ # Also remove CR from line-end
+ s/\015$//;
+
if ($_ ne @$content[$index])
{
$no_match = 1;
@@ -1052,21 +1315,6 @@ sub split_filename($)
#
-# get_dir(filename);
-#
-# Return the directory component of a given FILENAME.
-#
-
-sub get_dir($)
-{
- my @components = split("/", $_[0]);
- pop(@components);
-
- return join("/", @components);
-}
-
-
-#
# read_gcov_header(gcov_filename)
#
# Parse file GCOV_FILENAME and return a list containing the following
@@ -1102,6 +1350,9 @@ sub read_gcov_header($)
{
chomp($_);
+ # Also remove CR from line-end
+ s/\015$//;
+
if (/^\s+-:\s+0:Source:(.*)$/)
{
# Source: header entry
@@ -1125,6 +1376,84 @@ sub read_gcov_header($)
#
+# 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:
@@ -1137,8 +1466,8 @@ sub read_gcov_header($)
# $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 list of 2 elements
-# (linenumber, branch result) for each branch
+# 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
@@ -1150,13 +1479,23 @@ sub read_gcov_file($)
{
my $filename = $_[0];
my @result = ();
- my @branches = ();
+ 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;
- open(INPUT, $filename)
- or die("ERROR: cannot read $filename!\n");
+ 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)
{
@@ -1165,29 +1504,17 @@ sub read_gcov_file($)
{
chomp($_);
- if (/^\t\t(.*)$/)
- {
- # Uninstrumented line
- push(@result, 0);
- push(@result, 0);
- push(@result, $1);
- }
- elsif (/^branch/)
- {
- # Branch execution data
- push(@branches, scalar(@result) / 3);
- if (/^branch \d+ never executed$/)
- {
- push(@branches, 0);
- }
- elsif (/^branch \d+ taken = 0%/)
- {
- push(@branches, 1);
- }
- else
- {
- push(@branches, 2);
- }
+ # 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/)
{
@@ -1195,15 +1522,43 @@ sub read_gcov_file($)
}
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; }
- push(@result, 1);
- push(@result, $number);
+ if ($exclude_line) {
+ # Register uninstrumented line instead
+ push(@result, 0);
+ push(@result, 0);
+ } else {
+ push(@result, 1);
+ push(@result, $number);
+ }
push(@result, substr($_, 16));
}
}
@@ -1215,25 +1570,28 @@ sub read_gcov_file($)
{
chomp($_);
- if (/^branch\s+\d+\s+(\S+)\s+(\S+)/)
- {
- # Branch execution data
- push(@branches, scalar(@result) / 3);
- if ($1 eq "never")
- {
- push(@branches, 0);
- }
- elsif ($2 eq "0%")
- {
- push(@branches, 1);
- }
- else
- {
- push(@branches, 2);
- }
+ # 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/)
@@ -1242,580 +1600,59 @@ sub read_gcov_file($)
}
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 ($2 eq "0")
+ if ($line eq "0")
{
# Extra data
}
- elsif ($1 eq "-")
+ elsif ($count eq "-")
{
# Uninstrumented line
push(@result, 0);
push(@result, 0);
- push(@result, $3);
+ push(@result, $code);
}
else
{
- # Source code execution data
- $number = $1;
-
- # Check for zero count
- if ($number eq "#####") { $number = 0; }
-
- push(@result, 1);
- push(@result, $number);
- push(@result, $3);
- }
- }
- }
- }
-
- close(INPUT);
- return(\@result, \@branches, \@functions);
-}
-
-
-#
-# read_bb_file(bb_filename, base_dir)
-#
-# Read .bb file BB_FILENAME and return a hash containing the following
-# mapping:
-#
-# filename -> comma-separated list of pairs (function name=starting
-# line number) to indicate the starting line of a function or
-# =name to indicate an instrumented line
-#
-# for each entry in the .bb file. Filenames are absolute, i.e. relative
-# filenames are prefixed with BASE_DIR.
-#
-# Die on error.
-#
-
-sub read_bb_file($$)
-{
- my $bb_filename = $_[0];
- my $base_dir = $_[1];
- my %result;
- my $filename;
- my $function_name;
- my $minus_one = sprintf("%d", 0x80000001);
- my $minus_two = sprintf("%d", 0x80000002);
- my $value;
- my $packed_word;
- local *INPUT;
-
- open(INPUT, $bb_filename)
- or die("ERROR: cannot read $bb_filename!\n");
-
- binmode(INPUT);
-
- # Read data in words of 4 bytes
- while (read(INPUT, $packed_word, 4) == 4)
- {
- # Decode integer in intel byteorder
- $value = unpack_int32($packed_word, 0);
-
- # Note: the .bb file format is documented in GCC info pages
- if ($value == $minus_one)
- {
- # Filename follows
- $filename = read_string(*INPUT, $minus_one)
- or die("ERROR: incomplete filename in ".
- "$bb_filename!\n");
-
- # Make path absolute
- $filename = solve_relative_path($base_dir, $filename);
-
- # Insert into hash if not yet present.
- # This is necessary because functions declared as
- # "inline" are not listed as actual functions in
- # .bb files
- if (!$result{$filename})
- {
- $result{$filename}="";
- }
- }
- elsif ($value == $minus_two)
- {
- # Function name follows
- $function_name = read_string(*INPUT, $minus_two)
- or die("ERROR: incomplete function ".
- "name in $bb_filename!\n");
- $function_name =~ s/\W/_/g;
- }
- elsif ($value > 0)
- {
- if (defined($filename))
- {
- $result{$filename} .=
- ($result{$filename} ? "," : "").
- "=$value";
- }
- else
- {
- warn("WARNING: unassigned line".
- " number in .bb file ".
- "$bb_filename\n");
- }
- if ($function_name)
- {
- # Got a full entry filename, funcname, lineno
- # Add to resulting hash
-
- $result{$filename}.=
- ($result{$filename} ? "," : "").
- join("=",($function_name,$value));
- undef($function_name);
- }
- }
- }
- close(INPUT);
-
- if (!scalar(keys(%result)))
- {
- die("ERROR: no data found in $bb_filename!\n");
- }
- return %result;
-}
-
-
-#
-# read_string(handle, delimiter);
-#
-# Read and return a string in 4-byte chunks from HANDLE until DELIMITER
-# is found.
-#
-# Return empty string on error.
-#
-
-sub read_string(*$)
-{
- my $HANDLE = $_[0];
- my $delimiter = $_[1];
- my $string = "";
- my $packed_word;
- my $value;
-
- while (read($HANDLE,$packed_word,4) == 4)
- {
- $value = unpack_int32($packed_word, 0);
-
- if ($value == $delimiter)
- {
- # Remove trailing nil bytes
- $/="\0";
- while (chomp($string)) {};
- $/="\n";
- return($string);
- }
-
- $string = $string.$packed_word;
- }
- return("");
-}
-
-
-#
-# read_gcno_file(bb_filename, base_dir)
-#
-# Read .gcno file BB_FILENAME and return a hash containing the following
-# mapping:
-#
-# filename -> comma-separated list of pairs (function name=starting
-# line number) to indicate the starting line of a function or
-# =name to indicate an instrumented line
-#
-# for each entry in the .gcno file. Filenames are absolute, i.e. relative
-# filenames are prefixed with BASE_DIR.
-#
-# Die on error.
-#
-
-sub read_gcno_file($$)
-{
- my $gcno_filename = $_[0];
- my $base_dir = $_[1];
- my %result;
- my $filename;
- my $function_name;
- my $lineno;
- my $length;
- my $value;
- my $endianness;
- my $blocks;
- my $packed_word;
- my $string;
- local *INPUT;
-
- open(INPUT, $gcno_filename)
- or die("ERROR: cannot read $gcno_filename!\n");
-
- binmode(INPUT);
-
- read(INPUT, $packed_word, 4) == 4
- or die("ERROR: Invalid gcno file format\n");
-
- $value = unpack_int32($packed_word, 0);
- $endianness = !($value == $GCNO_FILE_MAGIC);
-
- unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC
- or die("ERROR: gcno file magic does not match\n");
-
- seek(INPUT, 8, 1);
-
- # Read data in words of 4 bytes
- while (read(INPUT, $packed_word, 4) == 4)
- {
- # Decode integer in intel byteorder
- $value = unpack_int32($packed_word, $endianness);
-
- if ($value == $GCNO_FUNCTION_TAG)
- {
- # skip length, ident and checksum
- seek(INPUT, 12, 1);
- (undef, $function_name) =
- read_gcno_string(*INPUT, $endianness);
- $function_name =~ s/\W/_/g;
- (undef, $filename) =
- read_gcno_string(*INPUT, $endianness);
- $filename = solve_relative_path($base_dir, $filename);
-
- read(INPUT, $packed_word, 4);
- $lineno = unpack_int32($packed_word, $endianness);
-
- $result{$filename}.=
- ($result{$filename} ? "," : "").
- join("=",($function_name,$lineno));
- }
- elsif ($value == $GCNO_LINES_TAG)
- {
- # Check for names of files containing inlined code
- # included in this file
- read(INPUT, $packed_word, 4);
- $length = unpack_int32($packed_word, $endianness);
- if ($length > 0)
- {
- # Block number
- read(INPUT, $packed_word, 4);
- $length--;
- }
- while ($length > 0)
- {
- read(INPUT, $packed_word, 4);
- $lineno = unpack_int32($packed_word,
- $endianness);
- $length--;
- if ($lineno != 0)
- {
- if (defined($filename))
- {
- $result{$filename} .=
- ($result{$filename} ? "," : "").
- "=$lineno";
+ 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);
}
- else
- {
- warn("WARNING: unassigned line".
- " number in .gcno file ".
- "$gcno_filename\n");
- }
- next;
+ push(@result, $code);
}
- last if ($length == 0);
- ($blocks, $string) =
- read_gcno_string(*INPUT, $endianness);
- if (defined($string))
- {
- $filename = $string;
- }
- if ($blocks > 1)
- {
- $filename = solve_relative_path(
- $base_dir, $filename);
- if (!defined($result{$filename}))
- {
- $result{$filename} = "";
- }
- }
- $length -= $blocks;
}
}
- else
- {
- read(INPUT, $packed_word, 4);
- $length = unpack_int32($packed_word, $endianness);
- seek(INPUT, 4 * $length, 1);
- }
- }
- close(INPUT);
-
- if (!scalar(keys(%result)))
- {
- die("ERROR: no data found in $gcno_filename!\n");
- }
- return %result;
-}
-
-
-#
-# read_gcno_string(handle, endianness);
-#
-# Read a string in 4-byte chunks from HANDLE.
-#
-# Return (number of 4-byte chunks read, string).
-#
-
-sub read_gcno_string(*$)
-{
- my $handle = $_[0];
- my $endianness = $_[1];
- my $number_of_blocks = 0;
- my $string = "";
- my $packed_word;
-
- read($handle, $packed_word, 4) == 4
- or die("ERROR: reading string\n");
-
- $number_of_blocks = unpack_int32($packed_word, $endianness);
-
- if ($number_of_blocks == 0)
- {
- return (1, undef);
- }
-
- if (read($handle, $packed_word, 4 * $number_of_blocks) !=
- 4 * $number_of_blocks)
- {
- my $msg = "invalid string size ".(4 * $number_of_blocks)." in ".
- "gcno file at position ".tell($handle)."\n";
- if ($ignore[$ERROR_SOURCE])
- {
- warn("WARNING: $msg");
- return (1, undef);
- }
- else
- {
- die("ERROR: $msg");
- }
}
- $string = $string . $packed_word;
-
- # Remove trailing nil bytes
- $/="\0";
- while (chomp($string)) {};
- $/="\n";
-
- return(1 + $number_of_blocks, $string);
-}
-
-
-#
-# read_hammer_bbg_file(bb_filename, base_dir)
-#
-# Read .bbg file BB_FILENAME and return a hash containing the following
-# mapping:
-#
-# filename -> comma-separated list of pairs (function name=starting
-# line number) to indicate the starting line of a function or
-# =name to indicate an instrumented line
-#
-# for each entry in the .bbg file. Filenames are absolute, i.e. relative
-# filenames are prefixed with BASE_DIR.
-#
-# Die on error.
-#
-
-sub read_hammer_bbg_file($$)
-{
- my $bbg_filename = $_[0];
- my $base_dir = $_[1];
- my %result;
- my $filename;
- my $function_name;
- my $first_line;
- my $lineno;
- my $length;
- my $value;
- my $endianness;
- my $blocks;
- my $packed_word;
- local *INPUT;
-
- open(INPUT, $bbg_filename)
- or die("ERROR: cannot read $bbg_filename!\n");
-
- binmode(INPUT);
-
- # Read magic
- read(INPUT, $packed_word, 4) == 4
- or die("ERROR: invalid bbg file format\n");
-
- $endianness = 1;
-
- unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC
- or die("ERROR: bbg file magic does not match\n");
-
- # Skip version
- seek(INPUT, 4, 1);
-
- # Read data in words of 4 bytes
- while (read(INPUT, $packed_word, 4) == 4)
- {
- # Get record tag
- $value = unpack_int32($packed_word, $endianness);
-
- # Get record length
- read(INPUT, $packed_word, 4);
- $length = unpack_int32($packed_word, $endianness);
-
- if ($value == $GCNO_FUNCTION_TAG)
- {
- # Get function name
- ($value, $function_name) =
- read_hammer_bbg_string(*INPUT, $endianness);
- $function_name =~ s/\W/_/g;
- $filename = undef;
- $first_line = undef;
-
- seek(INPUT, $length - $value * 4, 1);
- }
- elsif ($value == $GCNO_LINES_TAG)
- {
- # Get linenumber and filename
- # Skip block number
- seek(INPUT, 4, 1);
- $length -= 4;
-
- while ($length > 0)
- {
- read(INPUT, $packed_word, 4);
- $lineno = unpack_int32($packed_word,
- $endianness);
- $length -= 4;
- if ($lineno != 0)
- {
- if (!defined($first_line))
- {
- $first_line = $lineno;
- }
- if (defined($filename))
- {
- $result{$filename} .=
- ($result{$filename} ? "," : "").
- "=$lineno";
- }
- else
- {
- warn("WARNING: unassigned line".
- " number in .bbg file ".
- "$bbg_filename\n");
- }
- next;
- }
- ($blocks, $value) =
- read_hammer_bbg_string(
- *INPUT, $endianness);
- # Add all filenames to result list
- if (defined($value))
- {
- $value = solve_relative_path(
- $base_dir, $value);
- if (!defined($result{$value}))
- {
- $result{$value} = undef;
- }
- if (!defined($filename))
- {
- $filename = $value;
- }
- }
- $length -= $blocks * 4;
-
- # Got a complete data set?
- if (defined($filename) &&
- defined($first_line) &&
- defined($function_name))
- {
- # Add it to our result hash
- if (defined($result{$filename}))
- {
- $result{$filename} .=
- ",$function_name=$first_line";
- }
- else
- {
- $result{$filename} =
- "$function_name=$first_line";
- }
- $function_name = undef;
- $filename = undef;
- $first_line = undef;
- }
- }
- }
- else
- {
- # Skip other records
- seek(INPUT, $length, 1);
- }
- }
close(INPUT);
-
- if (!scalar(keys(%result)))
- {
- die("ERROR: no data found in $bbg_filename!\n");
- }
- return %result;
-}
-
-
-#
-# read_hammer_bbg_string(handle, endianness);
-#
-# Read a string in 4-byte chunks from HANDLE.
-#
-# Return (number of 4-byte chunks read, string).
-#
-
-sub read_hammer_bbg_string(*$)
-{
- my $handle = $_[0];
- my $endianness = $_[1];
- my $length = 0;
- my $string = "";
- my $packed_word;
- my $pad;
-
- read($handle, $packed_word, 4) == 4
- or die("ERROR: reading string\n");
-
- $length = unpack_int32($packed_word, $endianness);
- $pad = 4 - $length % 4;
-
- if ($length == 0)
- {
- return (1, undef);
+ if ($exclude_flag) {
+ warn("WARNING: unterminated exclusion section in $filename\n");
}
-
- read($handle, $string, $length) ==
- $length or die("ERROR: reading string\n");
- seek($handle, $pad, 1);
-
- return(1 + ($length + $pad) / 4, $string);
-}
-
-#
-# unpack_int32(word, endianness)
-#
-# Interpret 4-byte binary string WORD as signed 32 bit integer in
-# endian encoding defined by ENDIANNESS (0=little, 1=big) and return its
-# value.
-#
-
-sub unpack_int32($$)
-{
- return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0]));
+ return(\@result, $branches, \@functions);
}
@@ -2013,43 +1850,171 @@ sub apply_config($)
}
-sub gen_initial_info($)
+#
+# 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 $directory = $_[0];
- my @file_list;
+ my ($filename) = @_;
+ my %list;
+ my $flag = 0;
+ local *HANDLE;
- if (-d $directory)
- {
- info("Scanning $directory for $graph_file_extension ".
- "files ...\n");
+ 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);
- @file_list = `find "$directory" $maxdepth $follow -name \\*$graph_file_extension -type f 2>/dev/null`;
- chomp(@file_list);
- @file_list or die("ERROR: no $graph_file_extension files ".
- "found in $directory!\n");
- info("Found %d graph files in %s\n", $#file_list+1, $directory);
+ if ($flag) {
+ warn("WARNING: unterminated exclusion section in $filename\n");
}
- else
- {
- @file_list = ($directory);
+
+ 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);
}
- # Process all files in list
- foreach (@file_list) { process_graphfile($_); }
+ # 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($)
+
+sub process_graphfile($$)
{
- my $graph_filename = $_[0];
+ my ($file, $dir) = @_;
+ my $graph_filename = $file;
my $graph_dir;
my $graph_basename;
my $source_dir;
my $base_dir;
- my %graph_data;
+ my $graph;
+ my $instr;
my $filename;
local *INFO_HANDLE;
- info("Processing $_[0]\n");
+ info("Processing %s\n", abs2rel($file, $dir));
# Get path to data file in absolute and normalized form (begins with /,
# contains no more ../ or ./)
@@ -2079,17 +2044,21 @@ sub process_graphfile($)
{
if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
{
- %graph_data = read_hammer_bbg_file($graph_filename,
- $base_dir);
+ ($instr, $graph) = read_bbg($graph_filename, $base_dir);
}
else
{
- %graph_data = read_bb_file($graph_filename, $base_dir);
+ ($instr, $graph) = read_bb($graph_filename, $base_dir);
}
}
else
{
- %graph_data = read_gcno_file($graph_filename, $base_dir);
+ ($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
@@ -2116,45 +2085,45 @@ sub process_graphfile($)
# Write test name
printf(INFO_HANDLE "TN:%s\n", $test_name);
- foreach $filename (keys(%graph_data))
+ foreach $filename (sort(keys(%{$instr})))
{
- my %lines;
- my $count = 0;
- my @functions;
+ my $funcdata = $graph->{$filename};
+ my $line;
+ my $linedata;
print(INFO_HANDLE "SF:$filename\n");
- # Write function related data
- foreach (split(",",$graph_data{$filename}))
- {
- my ($fn, $line) = split("=", $_);
+ if (defined($funcdata)) {
+ my @functions = sort {$funcdata->{$a}->[0] <=>
+ $funcdata->{$b}->[0]}
+ keys(%{$funcdata});
+ my $func;
- if ($fn eq "")
- {
- $lines{$line} = "";
- next;
- }
-
- # Normalize function name
- $fn =~ s/\W/_/g;
+ # Gather list of instrumented lines and functions
+ foreach $func (@functions) {
+ $linedata = $funcdata->{$func};
- print(INFO_HANDLE "FN:$line,$fn\n");
- push(@functions, $fn);
- }
- foreach (@functions) {
- print(INFO_HANDLE "FNDA:$_,0\n");
+ # 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(INFO_HANDLE "FNF:".scalar(@functions)."\n");
- print(INFO_HANDLE "FNH:0\n");
-
- # Write line related data
- foreach (sort {$a <=> $b } keys(%lines))
- {
- print(INFO_HANDLE "DA:$_,0\n");
- $count++;
+ # 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 "LF:$count\n");
+
print(INFO_HANDLE "end_of_record\n");
}
if (!($output_filename && ($output_filename eq "-")))
@@ -2163,6 +2132,16 @@ sub process_graphfile($)
}
}
+sub filter_fn_name($)
+{
+ my ($fn) = @_;
+
+ # Remove characters used internally as function name delimiters
+ $fn =~ s/[,=]/_/g;
+
+ return $fn;
+}
+
sub warn_handler($)
{
my ($msg) = @_;
@@ -2176,3 +2155,914 @@ sub die_handler($)
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;
+}