summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '3rdParty/LCov/geninfo')
-rwxr-xr-x3rdParty/LCov/geninfo1200
1 files changed, 967 insertions, 233 deletions
diff --git a/3rdParty/LCov/geninfo b/3rdParty/LCov/geninfo
index dcb1a67..7c4e6cc 100755
--- a/3rdParty/LCov/geninfo
+++ b/3rdParty/LCov/geninfo
@@ -1,6 +1,6 @@
#!/usr/bin/perl -w
#
-# Copyright (c) International Business Machines Corp., 2002,2010
+# Copyright (c) International Business Machines Corp., 2002,2012
#
# 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
@@ -52,17 +52,23 @@
use strict;
use File::Basename;
use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir
- splitpath/;
+ splitpath catpath/;
use Getopt::Long;
use Digest::MD5 qw(md5_base64);
-
+use Cwd qw/abs_path/;
+if( $^O eq "msys" )
+{
+ require File::Spec::Win32;
+}
# Constants
-our $lcov_version = 'LCOV version 1.9';
+our $tool_dir = abs_path(dirname($0));
+our $lcov_version = "LCOV version 1.12";
our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php";
our $gcov_tool = "gcov";
our $tool_name = basename($0);
+our $GCOV_VERSION_4_7_0 = 0x40700;
our $GCOV_VERSION_3_4_0 = 0x30400;
our $GCOV_VERSION_3_3_0 = 0x30300;
our $GCNO_FUNCTION_TAG = 0x01000000;
@@ -70,15 +76,68 @@ our $GCNO_LINES_TAG = 0x01450000;
our $GCNO_FILE_MAGIC = 0x67636e6f;
our $BBG_FILE_MAGIC = 0x67626267;
-our $COMPAT_HAMMER = "hammer";
-
+# Error classes which users may specify to ignore during processing
our $ERROR_GCOV = 0;
our $ERROR_SOURCE = 1;
our $ERROR_GRAPH = 2;
+our %ERROR_ID = (
+ "gcov" => $ERROR_GCOV,
+ "source" => $ERROR_SOURCE,
+ "graph" => $ERROR_GRAPH,
+);
our $EXCL_START = "LCOV_EXCL_START";
our $EXCL_STOP = "LCOV_EXCL_STOP";
-our $EXCL_LINE = "LCOV_EXCL_LINE";
+
+# Marker to exclude branch coverage but keep function and line coveage
+our $EXCL_BR_START = "LCOV_EXCL_BR_START";
+our $EXCL_BR_STOP = "LCOV_EXCL_BR_STOP";
+
+# Compatibility mode values
+our $COMPAT_VALUE_OFF = 0;
+our $COMPAT_VALUE_ON = 1;
+our $COMPAT_VALUE_AUTO = 2;
+
+# Compatibility mode value names
+our %COMPAT_NAME_TO_VALUE = (
+ "off" => $COMPAT_VALUE_OFF,
+ "on" => $COMPAT_VALUE_ON,
+ "auto" => $COMPAT_VALUE_AUTO,
+);
+
+# Compatiblity modes
+our $COMPAT_MODE_LIBTOOL = 1 << 0;
+our $COMPAT_MODE_HAMMER = 1 << 1;
+our $COMPAT_MODE_SPLIT_CRC = 1 << 2;
+
+# Compatibility mode names
+our %COMPAT_NAME_TO_MODE = (
+ "libtool" => $COMPAT_MODE_LIBTOOL,
+ "hammer" => $COMPAT_MODE_HAMMER,
+ "split_crc" => $COMPAT_MODE_SPLIT_CRC,
+ "android_4_4_0" => $COMPAT_MODE_SPLIT_CRC,
+);
+
+# Map modes to names
+our %COMPAT_MODE_TO_NAME = (
+ $COMPAT_MODE_LIBTOOL => "libtool",
+ $COMPAT_MODE_HAMMER => "hammer",
+ $COMPAT_MODE_SPLIT_CRC => "split_crc",
+);
+
+# Compatibility mode default values
+our %COMPAT_MODE_DEFAULTS = (
+ $COMPAT_MODE_LIBTOOL => $COMPAT_VALUE_ON,
+ $COMPAT_MODE_HAMMER => $COMPAT_VALUE_AUTO,
+ $COMPAT_MODE_SPLIT_CRC => $COMPAT_VALUE_AUTO,
+);
+
+# Compatibility mode auto-detection routines
+sub compat_hammer_autodetect();
+our %COMPAT_MODE_AUTO = (
+ $COMPAT_MODE_HAMMER => \&compat_hammer_autodetect,
+ $COMPAT_MODE_SPLIT_CRC => 1, # will be done later
+);
our $BR_LINE = 0;
our $BR_BLOCK = 1;
@@ -86,8 +145,9 @@ our $BR_BRANCH = 2;
our $BR_TAKEN = 3;
our $BR_VEC_ENTRIES = 4;
our $BR_VEC_WIDTH = 32;
+our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH);
-our $UNNAMED_BLOCK = 9999;
+our $UNNAMED_BLOCK = -1;
# Prototypes
sub print_usage(*);
@@ -112,8 +172,9 @@ sub warn_handler($);
sub die_handler($);
sub graph_error($$);
sub graph_expect($);
-sub graph_read(*$;$);
+sub graph_read(*$;$$);
sub graph_skip(*$;$);
+sub uniq(@);
sub sort_uniq(@);
sub sort_uniq_lex(@);
sub graph_cleanup($);
@@ -123,18 +184,19 @@ sub graph_add_order($$$);
sub read_bb_word(*;$);
sub read_bb_value(*;$);
sub read_bb_string(*$);
-sub read_bb($$);
+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_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_lines_record(*$$$$$$);
+sub determine_gcno_split_crc($$$);
sub read_gcno_function_record(*$$$$);
-sub read_gcno($$);
+sub read_gcno($);
sub get_gcov_capabilities();
sub get_overall_line($$$$);
sub print_overall_rate($$$$$$$$$);
@@ -142,10 +204,17 @@ sub br_gvec_len($);
sub br_gvec_get($$);
sub debug($);
sub int_handler();
+sub parse_ignore_errors(@);
+sub is_external($);
+sub compat_name($);
+sub parse_compat_modes($);
+sub is_compat($);
+sub is_compat_auto($);
# Global variables
our $gcov_version;
+our $gcov_version_string;
our $graph_file_extension;
our $data_file_extension;
our @data_directory;
@@ -158,12 +227,13 @@ our $version;
our $follow;
our $checksum;
our $no_checksum;
-our $compat_libtool;
-our $no_compat_libtool;
+our $opt_compat_libtool;
+our $opt_no_compat_libtool;
+our $rc_adjust_src_path;# Regexp specifying parts to remove from source path
+our $adjust_src_pattern;
+our $adjust_src_replace;
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;
@@ -171,9 +241,23 @@ our $no_recursion = 0;
our $maxdepth;
our $no_markers = 0;
our $opt_derive_func_data = 0;
+our $opt_external = 1;
+our $opt_no_external;
our $debug = 0;
our $gcov_caps;
our @gcov_options;
+our @internal_dirs;
+our $opt_config_file;
+our $opt_gcov_all_blocks = 1;
+our $opt_compat;
+our %opt_rc;
+our %compat_value;
+our $gcno_split_crc;
+our $func_coverage = 1;
+our $br_coverage = 0;
+our $rc_auto_base = 1;
+our $excl_line = "LCOV_EXCL_LINE";
+our $excl_br_line = "LCOV_EXCL_BR_LINE";
our $cwd = `pwd`;
chomp($cwd);
@@ -188,14 +272,32 @@ $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 LC_ALL so that gcov output will be in a unified format
+$ENV{"LC_ALL"} = "C";
-# Set LANG so that gcov output will be in a unified format
-$ENV{"LANG"} = "C";
+# Check command line for a configuration file name
+Getopt::Long::Configure("pass_through", "no_auto_abbrev");
+GetOptions("config-file=s" => \$opt_config_file,
+ "rc=s%" => \%opt_rc);
+Getopt::Long::Configure("default");
+
+{
+ # Remove spaces around rc options
+ my %new_opt_rc;
+
+ while (my ($key, $value) = each(%opt_rc)) {
+ $key =~ s/^\s+|\s+$//g;
+ $value =~ s/^\s+|\s+$//g;
+
+ $new_opt_rc{$key} = $value;
+ }
+ %opt_rc = %new_opt_rc;
+}
# Read configuration file if available
-if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
+if (defined($opt_config_file)) {
+ $config = read_config($opt_config_file);
+} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
{
$config = read_config($ENV{"HOME"}."/.lcovrc");
}
@@ -204,15 +306,25 @@ elsif (-r "/etc/lcovrc")
$config = read_config("/etc/lcovrc");
}
-if ($config)
+if ($config || %opt_rc)
{
- # Copy configuration file values to variables
+ # Copy configuration file and --rc 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});
+ "geninfo_compat_libtool" => \$opt_compat_libtool,
+ "geninfo_external" => \$opt_external,
+ "geninfo_gcov_all_blocks" => \$opt_gcov_all_blocks,
+ "geninfo_compat" => \$opt_compat,
+ "geninfo_adjust_src_path" => \$rc_adjust_src_path,
+ "geninfo_auto_base" => \$rc_auto_base,
+ "lcov_function_coverage" => \$func_coverage,
+ "lcov_branch_coverage" => \$br_coverage,
+ "lcov_excl_line" => \$excl_line,
+ "lcov_excl_br_line" => \$excl_br_line,
+ });
# Merge options
if (defined($no_checksum))
@@ -220,6 +332,34 @@ if ($config)
$checksum = ($no_checksum ? 0 : 1);
$no_checksum = undef;
}
+
+ # Check regexp
+ if (defined($rc_adjust_src_path)) {
+ my ($pattern, $replace) = split(/\s*=>\s*/,
+ $rc_adjust_src_path);
+ local $SIG{__DIE__};
+ eval '$adjust_src_pattern = qr>'.$pattern.'>;';
+ if (!defined($adjust_src_pattern)) {
+ my $msg = $@;
+
+ chomp($msg);
+ $msg =~ s/at \(eval.*$//;
+ warn("WARNING: invalid pattern in ".
+ "geninfo_adjust_src_path: $msg\n");
+ } elsif (!defined($replace)) {
+ # If no replacement is specified, simply remove pattern
+ $adjust_src_replace = "";
+ } else {
+ $adjust_src_replace = $replace;
+ }
+ }
+ for my $regexp (($excl_line, $excl_br_line)) {
+ eval 'qr/'.$regexp.'/';
+ my $error = $@;
+ chomp($error);
+ $error =~ s/at \(eval.*$//;
+ die("ERROR: invalid exclude pattern: $error") if $error;
+ }
}
# Parse command line options
@@ -232,8 +372,8 @@ if (!GetOptions("test-name|t=s" => \$test_name,
"quiet|q" => \$quiet,
"help|h|?" => \$help,
"follow|f" => \$follow,
- "compat-libtool" => \$compat_libtool,
- "no-compat-libtool" => \$no_compat_libtool,
+ "compat-libtool" => \$opt_compat_libtool,
+ "no-compat-libtool" => \$opt_no_compat_libtool,
"gcov-tool=s" => \$gcov_tool,
"ignore-errors=s" => \@ignore_errors,
"initial|i" => \$initial,
@@ -241,6 +381,11 @@ if (!GetOptions("test-name|t=s" => \$test_name,
"no-markers" => \$no_markers,
"derive-func-data" => \$opt_derive_func_data,
"debug" => \$debug,
+ "external" => \$opt_external,
+ "no-external" => \$opt_no_external,
+ "compat=s" => \$opt_compat,
+ "config-file=s" => \$opt_config_file,
+ "rc=s%" => \%opt_rc,
))
{
print(STDERR "Use $tool_name --help to get usage information\n");
@@ -255,10 +400,15 @@ else
$no_checksum = undef;
}
- if (defined($no_compat_libtool))
+ if (defined($opt_no_compat_libtool))
{
- $compat_libtool = ($no_compat_libtool ? 0 : 1);
- $no_compat_libtool = undef;
+ $opt_compat_libtool = ($opt_no_compat_libtool ? 0 : 1);
+ $opt_no_compat_libtool = undef;
+ }
+
+ if (defined($opt_no_external)) {
+ $opt_external = 0;
+ $opt_no_external = undef;
}
}
@@ -278,6 +428,30 @@ if ($version)
exit(0);
}
+# Check gcov tool
+if (system_no_output(3, $gcov_tool, "--help") == -1)
+{
+ die("ERROR: need tool $gcov_tool!\n");
+}
+
+($gcov_version, $gcov_version_string) = get_gcov_version();
+
+# Determine gcov options
+$gcov_caps = get_gcov_capabilities();
+push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'} &&
+ ($br_coverage || $func_coverage));
+push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'} &&
+ $br_coverage);
+push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'} &&
+ $opt_gcov_all_blocks && $br_coverage);
+push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'});
+
+# Determine compatibility modes
+parse_compat_modes($opt_compat);
+
+# Determine which errors the user wants us to ignore
+parse_ignore_errors(@ignore_errors);
+
# Make sure test names only contain valid characters
if ($test_name =~ s/\W/_/g)
{
@@ -319,17 +493,6 @@ else
$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)
{
@@ -358,42 +521,9 @@ else
}
}
-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)
+ if (is_compat($COMPAT_MODE_HAMMER))
{
$data_file_extension = ".da";
$graph_file_extension = ".bbg";
@@ -410,20 +540,13 @@ else
$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")
+ open(DUMMY_HANDLE, ">", $output_filename)
or die("ERROR: cannot create $output_filename!\n");
close(DUMMY_HANDLE);
@@ -435,12 +558,18 @@ if (defined($output_filename) && ($output_filename ne "-"))
}
}
+# Build list of directories to identify external files
+foreach my $entry(@data_directory, $base_directory) {
+ next if (!defined($entry));
+ push(@internal_dirs, solve_relative_path($cwd, $entry));
+}
+
# Do something
foreach my $entry (@data_directory) {
gen_info($entry);
}
-if ($initial) {
+if ($initial && $br_coverage) {
warn("Note: --initial does not generate branch coverage ".
"data\n");
}
@@ -480,9 +609,12 @@ sequentially.
--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
+ --(no-)external Include (ignore) data for external files
+ --config-file FILENAME Specify configuration file location
+ --rc SETTING=VALUE Override configuration file setting
+ --compat MODE=on|off|auto Set compat MODE (libtool, hammer, split_crc)
For more information see: $lcov_url
END_OF_USAGE
@@ -578,10 +710,13 @@ sub gen_info($)
{
info("Scanning $directory for $ext files ...\n");
- @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`;
+ @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f -o -name \\*$ext -type l 2>/dev/null`;
chomp(@file_list);
- @file_list or
- die("ERROR: no $ext files found in $directory!\n");
+ if (!@file_list) {
+ warn("WARNING: no $ext files found in $directory - ".
+ "skipping!\n");
+ return;
+ }
$prefix = get_common_prefix(1, @file_list);
info("Found %d %s files in %s\n", $#file_list+1, $type,
$directory);
@@ -604,6 +739,25 @@ sub gen_info($)
}
+#
+# derive_data(contentdata, funcdata, bbdata)
+#
+# Calculate function coverage data by combining line coverage data and the
+# list of lines belonging to a function.
+#
+# contentdata: [ instr1, count1, source1, instr2, count2, source2, ... ]
+# instr<n>: Instrumentation flag for line n
+# count<n>: Execution count for line n
+# source<n>: Source code for line n
+#
+# funcdata: [ count1, func1, count2, func2, ... ]
+# count<n>: Execution count for function number n
+# func<n>: Function name for function number n
+#
+# bbdata: function_name -> [ line1, line2, ... ]
+# line<n>: Line number belonging to the corresponding function
+#
+
sub derive_data($$$)
{
my ($contentdata, $funcdata, $bbdata) = @_;
@@ -633,6 +787,7 @@ sub derive_data($$$)
foreach $fn (keys(%{$bbdata})) {
my $line_data = $bbdata->{$fn};
my $line;
+ my $fninstr = 0;
if ($fn eq "") {
next;
@@ -640,13 +795,17 @@ sub derive_data($$$)
# Find the lowest line count for this function
$count = 0;
foreach $line (@$line_data) {
+ my $linstr = $gcov_content[ ( $line - 1 ) * 3 + 0 ];
my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ];
+ next if (!$linstr);
+ $fninstr = 1;
if (($lcount > 0) &&
(($count == 0) || ($lcount < $count))) {
$count = $lcount;
}
}
+ next if (!$fninstr);
$fn_count{$fn} = $count;
}
@@ -733,7 +892,6 @@ sub process_dafile($$)
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;
@@ -749,11 +907,10 @@ sub process_dafile($$)
# 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;
+ $source_dir = $da_dir;
+ if (is_compat($COMPAT_MODE_LIBTOOL)) {
+ # Avoid files from .libs dirs
+ $source_dir =~ s/\.libs$//;
}
if (-z $da_filename)
@@ -808,20 +965,27 @@ sub process_dafile($$)
# information about functions and their source code positions.
if ($gcov_version < $GCOV_VERSION_3_4_0)
{
- if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
+ if (is_compat($COMPAT_MODE_HAMMER))
{
- ($instr, $graph) = read_bbg($bb_filename, $base_dir);
+ ($instr, $graph) = read_bbg($bb_filename);
}
else
{
- ($instr, $graph) = read_bb($bb_filename, $base_dir);
+ ($instr, $graph) = read_bb($bb_filename);
}
}
else
{
- ($instr, $graph) = read_gcno($bb_filename, $base_dir);
+ ($instr, $graph) = read_gcno($bb_filename);
}
+ # Try to find base directory automatically if requested by user
+ if ($rc_auto_base) {
+ $base_dir = find_base_from_graph($base_dir, $instr, $graph);
+ }
+
+ ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph);
+
# 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.
@@ -850,6 +1014,7 @@ sub process_dafile($$)
}
# Change to directory containing data files and apply GCOV
+ debug("chdir($base_dir)\n");
chdir($base_dir);
if ($da_renamed)
@@ -905,7 +1070,7 @@ sub process_dafile($$)
else
{
# Append to output file
- open(INFO_HANDLE, ">>$output_filename")
+ open(INFO_HANDLE, ">>", $output_filename)
or die("ERROR: cannot write to ".
"$output_filename!\n");
}
@@ -913,7 +1078,7 @@ sub process_dafile($$)
else
{
# Open .info file for output
- open(INFO_HANDLE, ">$da_filename.info")
+ open(INFO_HANDLE, ">", "$da_filename.info")
or die("ERROR: cannot create $da_filename.info!\n");
}
@@ -922,23 +1087,34 @@ sub process_dafile($$)
# 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;
+ # Skip gcov file for gcc built-in code
+ next if ($gcov_file eq "<built-in>.gcov");
+
($source, $object) = read_gcov_header($gcov_file);
- if (defined($source))
- {
- $source = solve_relative_path($base_dir, $source);
+ if (!defined($source)) {
+ # Derive source file name from gcov file name if
+ # header format could not be parsed
+ $source = $gcov_file;
+ $source =~ s/\.gcov$//;
+ }
+
+ $source = solve_relative_path($base_dir, $source);
+
+ if (defined($adjust_src_pattern)) {
+ # Apply transformation as specified by user
+ $source =~ s/$adjust_src_pattern/$adjust_src_replace/g;
}
# 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 (! -r $source && $checksum)
{
if ($ignore[$ERROR_SOURCE])
{
@@ -949,8 +1125,7 @@ sub process_dafile($$)
die("ERROR: could not read source file $source\n");
}
- @matches = match_filename(defined($source) ? $source :
- $gcov_file, keys(%{$instr}));
+ @matches = match_filename($source, keys(%{$instr}));
# Skip files that are not mentioned in the graph file
if (!@matches)
@@ -994,13 +1169,13 @@ sub process_dafile($$)
\@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;
+ # Skip external files if requested
+ if (!$opt_external) {
+ if (is_external($source_filename)) {
+ info(" ignoring data for external file ".
+ "$source_filename\n");
+ unlink($gcov_file);
+ next;
}
}
@@ -1090,6 +1265,7 @@ sub process_dafile($$)
my ($line, $block, $branch, $taken) =
br_gvec_get($gcov_branches, $i);
+ $block = $BR_VEC_MAX if ($block < 0);
print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n");
$br_found++;
$br_hit++ if ($taken ne '-' && $taken > 0);
@@ -1138,16 +1314,6 @@ sub process_dafile($$)
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);
@@ -1168,8 +1334,40 @@ sub solve_relative_path($$)
{
my $path = $_[0];
my $dir = $_[1];
+ my $volume;
+ my $directories;
+ my $filename;
+ my @dirs; # holds path elements
my $result;
+ # Convert from Windows path to msys path
+ if( $^O eq "msys" )
+ {
+ # search for a windows drive letter at the beginning
+ ($volume, $directories, $filename) = File::Spec::Win32->splitpath( $dir );
+ if( $volume ne '' )
+ {
+ my $uppercase_volume;
+ # transform c/d\../e/f\g to Windows style c\d\..\e\f\g
+ $dir = File::Spec::Win32->canonpath( $dir );
+ # use Win32 module to retrieve path components
+ # $uppercase_volume is not used any further
+ ( $uppercase_volume, $directories, $filename ) = File::Spec::Win32->splitpath( $dir );
+ @dirs = File::Spec::Win32->splitdir( $directories );
+
+ # prepend volume, since in msys C: is always mounted to /c
+ $volume =~ s|^([a-zA-Z]+):|/\L$1\E|;
+ unshift( @dirs, $volume );
+
+ # transform to Unix style '/' path
+ $directories = File::Spec->catdir( @dirs );
+ $dir = File::Spec->catpath( '', $directories, $filename );
+ } else {
+ # eliminate '\' path separators
+ $dir = File::Spec->canonpath( $dir );
+ }
+ }
+
$result = $dir;
# Prepend path if not absolute
if ($dir =~ /^[^\/]/)
@@ -1182,6 +1380,10 @@ sub solve_relative_path($$)
# Remove .
$result =~ s/\/\.\//\//g;
+ $result =~ s/\/\.$/\//g;
+
+ # Remove trailing /
+ $result =~ s/\/$//g;
# Solve ..
while ($result =~ s/\/[^\/]+\/\.\.\//\//)
@@ -1266,7 +1468,7 @@ sub solve_ambiguous_match($$$)
{
# Compare file contents
- open(SOURCE, $filename)
+ open(SOURCE, "<", $filename)
or die("ERROR: cannot read $filename!\n");
$no_match = 0;
@@ -1336,7 +1538,7 @@ sub read_gcov_header($)
my $object;
local *INPUT;
- if (!open(INPUT, $_[0]))
+ if (!open(INPUT, "<", $_[0]))
{
if ($ignore_errors[$ERROR_GCOV])
{
@@ -1408,6 +1610,7 @@ sub br_gvec_get($$)
# Retrieve data from vector
$line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH);
$block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
+ $block = -1 if ($block == $BR_VEC_MAX);
$branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
$taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
@@ -1435,6 +1638,7 @@ sub br_gvec_push($$$$$)
$vec = "" if (!defined($vec));
$offset = br_gvec_len($vec) * $BR_VEC_ENTRIES;
+ $block = $BR_VEC_MAX if $block < 0;
# Encode taken value into an integer
if ($taken eq "-") {
@@ -1484,11 +1688,13 @@ sub read_gcov_file($)
my $number;
my $exclude_flag = 0;
my $exclude_line = 0;
+ my $exclude_br_flag = 0;
+ my $exclude_branch = 0;
my $last_block = $UNNAMED_BLOCK;
my $last_line = 0;
local *INPUT;
- if (!open(INPUT, $filename)) {
+ if (!open(INPUT, "<", $filename)) {
if ($ignore_errors[$ERROR_GCOV])
{
warn("WARNING: cannot read $filename!\n");
@@ -1508,11 +1714,15 @@ sub read_gcov_file($)
s/\015$//;
if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) {
+ next if (!$br_coverage);
next if ($exclude_line);
+ next if ($exclude_branch);
$branches = br_gvec_push($branches, $last_line,
$last_block, $1, $2);
} elsif (/^branch\s+(\d+)\s+never\s+executed/) {
+ next if (!$br_coverage);
next if ($exclude_line);
+ next if ($exclude_branch);
$branches = br_gvec_push($branches, $last_line,
$last_block, $1, '-');
}
@@ -1530,12 +1740,25 @@ sub read_gcov_file($)
} elsif (/$EXCL_START/) {
$exclude_flag = 1;
}
- if (/$EXCL_LINE/ || $exclude_flag) {
+ if (/$excl_line/ || $exclude_flag) {
$exclude_line = 1;
} else {
$exclude_line = 0;
}
}
+ # Check for exclusion markers (branch exclude)
+ if (!$no_markers) {
+ if (/$EXCL_BR_STOP/) {
+ $exclude_br_flag = 0;
+ } elsif (/$EXCL_BR_START/) {
+ $exclude_br_flag = 1;
+ }
+ if (/$excl_br_line/ || $exclude_br_flag) {
+ $exclude_branch = 1;
+ } else {
+ $exclude_branch = 0;
+ }
+ }
# Source code execution data
if (/^\t\t(.*)$/)
{
@@ -1579,16 +1802,21 @@ sub read_gcov_file($)
$last_line = $2;
$last_block = $3;
} elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) {
+ next if (!$br_coverage);
next if ($exclude_line);
+ next if ($exclude_branch);
$branches = br_gvec_push($branches, $last_line,
$last_block, $1, $2);
} elsif (/^branch\s+(\d+)\s+never\s+executed/) {
+ next if (!$br_coverage);
next if ($exclude_line);
+ next if ($exclude_branch);
$branches = br_gvec_push($branches, $last_line,
$last_block, $1, '-');
}
- elsif (/^function\s+(\S+)\s+called\s+(\d+)/)
+ elsif (/^function\s+(.+)\s+called\s+(\d+)\s+/)
{
+ next if (!$func_coverage);
if ($exclude_line) {
next;
}
@@ -1611,12 +1839,26 @@ sub read_gcov_file($)
} elsif (/$EXCL_START/) {
$exclude_flag = 1;
}
- if (/$EXCL_LINE/ || $exclude_flag) {
+ if (/$excl_line/ || $exclude_flag) {
$exclude_line = 1;
} else {
$exclude_line = 0;
}
}
+ # Check for exclusion markers (branch exclude)
+ if (!$no_markers) {
+ if (/$EXCL_BR_STOP/) {
+ $exclude_br_flag = 0;
+ } elsif (/$EXCL_BR_START/) {
+ $exclude_br_flag = 1;
+ }
+ if (/$excl_br_line/ || $exclude_br_flag) {
+ $exclude_branch = 1;
+ } else {
+ $exclude_branch = 0;
+ }
+ }
+
# <exec count>:<line number>:<source code>
if ($line eq "0")
{
@@ -1636,7 +1878,7 @@ sub read_gcov_file($)
push(@result, 0);
} else {
# Check for zero count
- if ($count eq "#####") {
+ if ($count =~ /^[#=]/) {
$count = 0;
}
push(@result, 1);
@@ -1649,7 +1891,7 @@ sub read_gcov_file($)
}
close(INPUT);
- if ($exclude_flag) {
+ if ($exclude_flag || $exclude_br_flag) {
warn("WARNING: unterminated exclusion section in $filename\n");
}
return(\@result, $branches, \@functions);
@@ -1667,12 +1909,29 @@ sub get_gcov_version()
local *HANDLE;
my $version_string;
my $result;
+ my $pipe_next_line;
- open(GCOV_PIPE, "$gcov_tool -v |")
+ open(GCOV_PIPE, "-|", "$gcov_tool --version")
or die("ERROR: cannot retrieve gcov version!\n");
$version_string = <GCOV_PIPE>;
+ # LLVM gcov keeps version information on the second line.
+ # For example, gcov --version yields:
+ # LLVM (http://llvm.org/):
+ # LLVM version 3.4svn
+
+ $pipe_next_line = <GCOV_PIPE>;
+ # In case version information is on first line.
+ # For example, with Xcode 7.0 gcov --version yields:
+ # Apple LLVM 7.0.0 (clang-700.0.65)
+
+ $version_string = $pipe_next_line if ($pipe_next_line && $version_string =~ /LLVM/);
close(GCOV_PIPE);
+ # Remove version information in parenthesis to cope with the following:
+ # - gcov (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3)
+ # - gcov (crosstool-NG 1.18.0) 4.7.2
+ $version_string =~ s/\([^\)]*\)//g;
+
$result = 0;
if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/)
{
@@ -1687,13 +1946,22 @@ sub get_gcov_version()
$result = $1 << 16 | $2 << 8;
}
}
- if ($version_string =~ /suse/i && $result == 0x30303 ||
- $version_string =~ /mandrake/i && $result == 0x30302)
+ if ($version_string =~ /LLVM/)
{
- info("Using compatibility mode for GCC 3.3 (hammer)\n");
- $compatibility = $COMPAT_HAMMER;
+ # Map LLVM versions to the version of GCC gcov which
+ # they emulate
+ if ($result >= 0x030400)
+ {
+ info("Found LLVM gcov version 3.4, which emulates gcov version 4.2\n");
+ $result = 0x040200;
+ }
+ else
+ {
+ warn("This version of LLVM's gcov is unknown. Assuming it emulates GCC gcov version 4.2.\n");
+ $result = 0x040200;
+ }
}
- return $result;
+ return ($result, $version_string);
}
@@ -1756,13 +2024,14 @@ sub system_no_output($@)
local *OLD_STDOUT;
# Save old stdout and stderr handles
- ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT");
- ($mode & 2) && open(OLD_STDERR, ">>&STDERR");
+ ($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");
+ ($mode & 1) && open(STDOUT, ">", "/dev/null");
+ ($mode & 2) && open(STDERR, ">", "/dev/null");
+ debug("system(".join(' ', @_).")\n");
system(@_);
$result = $?;
@@ -1771,8 +2040,8 @@ sub system_no_output($@)
($mode & 2) && close(STDERR);
# Restore old handles
- ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT");
- ($mode & 2) && open(STDERR, ">>&OLD_STDERR");
+ ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT");
+ ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR");
return $result;
}
@@ -1793,7 +2062,7 @@ sub read_config($)
my $value;
local *HANDLE;
- if (!open(HANDLE, "<$filename"))
+ if (!open(HANDLE, "<", $filename))
{
warn("WARNING: cannot read configuration file $filename\n");
return undef;
@@ -1832,8 +2101,8 @@ sub read_config($)
# 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.
+# variable. If the global configuration hashes CONFIG or OPT_RC contain a value
+# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
#
sub apply_config($)
@@ -1842,8 +2111,9 @@ sub apply_config($)
foreach (keys(%{$ref}))
{
- if (defined($config->{$_}))
- {
+ if (defined($opt_rc{$_})) {
+ ${$ref->{$_}} = $opt_rc{$_};
+ } elsif (defined($config->{$_})) {
${$ref->{$_}} = $config->{$_};
}
}
@@ -1865,7 +2135,7 @@ sub get_exclusion_data($)
my $flag = 0;
local *HANDLE;
- if (!open(HANDLE, "<$filename")) {
+ if (!open(HANDLE, "<", $filename)) {
warn("WARNING: could not open $filename\n");
return undef;
}
@@ -1875,7 +2145,7 @@ sub get_exclusion_data($)
} elsif (/$EXCL_START/) {
$flag = 1;
}
- if (/$EXCL_LINE/ || $flag) {
+ if (/$excl_line/ || $flag) {
$list{$.} = 1;
}
}
@@ -1989,12 +2259,7 @@ sub apply_exclusion_data($$)
}
# Store modified list
- if (scalar(@new_data) > 0) {
- $instr->{$filename} = \@new_data;
- } else {
- # All of this file was excluded
- delete($instr->{$filename});
- }
+ $instr->{$filename} = \@new_data;
}
return ($instr, $graph);
@@ -2023,11 +2288,10 @@ sub process_graphfile($$)
# 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;
+ $source_dir = $graph_dir;
+ if (is_compat($COMPAT_MODE_LIBTOOL)) {
+ # Avoid files from .libs dirs
+ $source_dir =~ s/\.libs$//;
}
# Construct base_dir for current file
@@ -2040,22 +2304,36 @@ sub process_graphfile($$)
$base_dir = $source_dir;
}
+ # Ignore empty graph file (e.g. source file with no statement)
+ if (-z $graph_filename)
+ {
+ warn("WARNING: empty $graph_filename (skipped)\n");
+ return;
+ }
+
if ($gcov_version < $GCOV_VERSION_3_4_0)
{
- if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER)
+ if (is_compat($COMPAT_MODE_HAMMER))
{
- ($instr, $graph) = read_bbg($graph_filename, $base_dir);
+ ($instr, $graph) = read_bbg($graph_filename);
}
else
{
- ($instr, $graph) = read_bb($graph_filename, $base_dir);
+ ($instr, $graph) = read_bb($graph_filename);
}
}
else
{
- ($instr, $graph) = read_gcno($graph_filename, $base_dir);
+ ($instr, $graph) = read_gcno($graph_filename);
}
+ # Try to find base directory automatically if requested by user
+ if ($rc_auto_base) {
+ $base_dir = find_base_from_graph($base_dir, $instr, $graph);
+ }
+
+ ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph);
+
if (!$no_markers) {
# Apply exclusion marker data to graph file data
($instr, $graph) = apply_exclusion_data($instr, $graph);
@@ -2071,7 +2349,7 @@ sub process_graphfile($$)
else
{
# Append to output file
- open(INFO_HANDLE, ">>$output_filename")
+ open(INFO_HANDLE, ">>", $output_filename)
or die("ERROR: cannot write to ".
"$output_filename!\n");
}
@@ -2079,7 +2357,7 @@ sub process_graphfile($$)
else
{
# Open .info file for output
- open(INFO_HANDLE, ">$graph_filename.info")
+ open(INFO_HANDLE, ">", "$graph_filename.info")
or die("ERROR: cannot create $graph_filename.info!\n");
}
@@ -2091,9 +2369,18 @@ sub process_graphfile($$)
my $line;
my $linedata;
+ # Skip external files if requested
+ if (!$opt_external) {
+ if (is_external($filename)) {
+ info(" ignoring data for external file ".
+ "$filename\n");
+ next;
+ }
+ }
+
print(INFO_HANDLE "SF:$filename\n");
- if (defined($funcdata)) {
+ if (defined($funcdata) && $func_coverage) {
my @functions = sort {$funcdata->{$a}->[0] <=>
$funcdata->{$b}->[0]}
keys(%{$funcdata});
@@ -2194,26 +2481,36 @@ sub graph_expect($)
}
#
-# graph_read(handle, bytes[, description])
+# graph_read(handle, bytes[, description, peek])
#
# Read and return the specified number of bytes from handle. Return undef
-# if the number of bytes could not be read.
+# if the number of bytes could not be read. If PEEK is non-zero, reset
+# file position after read.
#
-sub graph_read(*$;$)
+sub graph_read(*$;$$)
{
- my ($handle, $length, $desc) = @_;
+ my ($handle, $length, $desc, $peek) = @_;
my $data;
my $result;
+ my $pos;
graph_expect($desc);
+ if ($peek) {
+ $pos = tell($handle);
+ if ($pos == -1) {
+ warn("Could not get current file position: $!\n");
+ return undef;
+ }
+ }
$result = read($handle, $data, $length);
if ($debug) {
+ my $op = $peek ? "peek" : "read";
my $ascii = "";
my $hex = "";
my $i;
- print(STDERR "DEBUG: read($length)=$result: ");
+ print(STDERR "DEBUG: $op($length)=$result: ");
for ($i = 0; $i < length($data); $i++) {
my $c = substr($data, $i, 1);;
my $n = ord($c);
@@ -2228,6 +2525,12 @@ sub graph_read(*$;$)
print(STDERR "$hex |$ascii|");
print(STDERR "\n");
}
+ if ($peek) {
+ if (!seek($handle, $pos, 0)) {
+ warn("Could not set file position: $!\n");
+ return undef;
+ }
+ }
if ($result != $length) {
return undef;
}
@@ -2252,6 +2555,27 @@ sub graph_skip(*$;$)
}
#
+# uniq(list)
+#
+# Return list without duplicate entries.
+#
+
+sub uniq(@)
+{
+ my (@list) = @_;
+ my @new_list;
+ my %known;
+
+ foreach my $item (@list) {
+ next if ($known{$item});
+ $known{$item} = 1;
+ push(@new_list, $item);
+ }
+
+ return @new_list;
+}
+
+#
# sort_uniq(list)
#
# Return list in numerically ascending order and without duplicate entries.
@@ -2286,6 +2610,133 @@ sub sort_uniq_lex(@)
}
#
+# parent_dir(dir)
+#
+# Return parent directory for DIR. DIR must not contain relative path
+# components.
+#
+
+sub parent_dir($)
+{
+ my ($dir) = @_;
+ my ($v, $d, $f) = splitpath($dir, 1);
+ my @dirs = splitdir($d);
+
+ pop(@dirs);
+
+ return catpath($v, catdir(@dirs), $f);
+}
+
+#
+# find_base_from_graph(base_dir, instr, graph)
+#
+# Try to determine the base directory of the graph file specified by INSTR
+# and GRAPH. The base directory is the base for all relative filenames in
+# the graph file. It is defined by the current working directory at time
+# of compiling the source file.
+#
+# This function implements a heuristic which relies on the following
+# assumptions:
+# - all files used for compilation are still present at their location
+# - the base directory is either BASE_DIR or one of its parent directories
+# - files by the same name are not present in multiple parent directories
+#
+
+sub find_base_from_graph($$$)
+{
+ my ($base_dir, $instr, $graph) = @_;
+ my $old_base;
+ my $best_miss;
+ my $best_base;
+ my %rel_files;
+
+ # Determine list of relative paths
+ foreach my $filename (keys(%{$instr}), keys(%{$graph})) {
+ next if (file_name_is_absolute($filename));
+
+ $rel_files{$filename} = 1;
+ }
+
+ # Early exit if there are no relative paths
+ return $base_dir if (!%rel_files);
+
+ do {
+ my $miss = 0;
+
+ foreach my $filename (keys(%rel_files)) {
+ if (!-e solve_relative_path($base_dir, $filename)) {
+ $miss++;
+ }
+ }
+
+ debug("base_dir=$base_dir miss=$miss\n");
+
+ # Exit if we find an exact match with no misses
+ return $base_dir if ($miss == 0);
+
+ # No exact match, aim for the one with the least source file
+ # misses
+ if (!defined($best_base) || $miss < $best_miss) {
+ $best_base = $base_dir;
+ $best_miss = $miss;
+ }
+
+ # Repeat until there's no more parent directory
+ $old_base = $base_dir;
+ $base_dir = parent_dir($base_dir);
+ } while ($old_base ne $base_dir);
+
+ return $best_base;
+}
+
+#
+# adjust_graph_filenames(base_dir, instr, graph)
+#
+# Make relative paths in INSTR and GRAPH absolute and apply
+# geninfo_adjust_src_path setting to graph file data.
+#
+
+sub adjust_graph_filenames($$$)
+{
+ my ($base_dir, $instr, $graph) = @_;
+
+ foreach my $filename (keys(%{$instr})) {
+ my $old_filename = $filename;
+
+ # Convert to absolute canonical form
+ $filename = solve_relative_path($base_dir, $filename);
+
+ # Apply adjustment
+ if (defined($adjust_src_pattern)) {
+ $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g;
+ }
+
+ if ($filename ne $old_filename) {
+ $instr->{$filename} = delete($instr->{$old_filename});
+ }
+ }
+
+ foreach my $filename (keys(%{$graph})) {
+ my $old_filename = $filename;
+
+ # Make absolute
+ # Convert to absolute canonical form
+ $filename = solve_relative_path($base_dir, $filename);
+
+ # Apply adjustment
+ if (defined($adjust_src_pattern)) {
+ $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g;
+ }
+
+ if ($filename ne $old_filename) {
+ $graph->{$filename} = delete($graph->{$old_filename});
+ }
+ }
+
+ return ($instr, $graph);
+}
+
+#
# graph_cleanup(graph)
#
# Remove entries for functions with no lines. Remove duplicate line numbers.
@@ -2310,7 +2761,7 @@ sub graph_cleanup($)
next;
}
# Normalize list
- $per_file->{$function} = [ sort_uniq(@$lines) ];
+ $per_file->{$function} = [ uniq(@$lines) ];
}
if (scalar(keys(%{$per_file})) == 0) {
# Remove empty file
@@ -2454,6 +2905,7 @@ sub graph_add_order($$$)
push(@$list, $filename);
$fileorder->{$function} = $list;
}
+
#
# read_bb_word(handle[, description])
#
@@ -2513,7 +2965,7 @@ sub read_bb_string(*$)
}
#
-# read_bb(filename, base_dir)
+# read_bb(filename)
#
# Read the contents of the specified .bb file and return (instr, graph), where:
#
@@ -2524,14 +2976,12 @@ sub read_bb_string(*$)
# 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.
+# See the gcov info pages of gcc 2.95 for a description of the .bb file format.
#
-sub read_bb($$)
+sub read_bb($)
{
- my ($bb_filename, $base) = @_;
+ my ($bb_filename) = @_;
my $minus_one = 0x80000001;
my $minus_two = 0x80000002;
my $value;
@@ -2543,7 +2993,7 @@ sub read_bb($$)
my $graph;
local *HANDLE;
- open(HANDLE, "<$bb_filename") or goto open_error;
+ open(HANDLE, "<", $bb_filename) or goto open_error;
binmode(HANDLE);
while (!eof(HANDLE)) {
$value = read_bb_value(*HANDLE, "data word");
@@ -2553,10 +3003,6 @@ sub read_bb($$)
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");
@@ -2647,16 +3093,15 @@ sub read_bbg_string(*)
#
# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename,
-# function, base)
+# function)
#
# 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(*$$$$$$)
+sub read_bbg_lines_record(*$$$$$)
{
- my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function,
- $base) = @_;
+ my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_;
my $string;
my $lineno;
@@ -2676,7 +3121,10 @@ sub read_bbg_lines_record(*$$$$$$)
if ($string eq "") {
return $filename;
}
- $filename = solve_relative_path($base, $string);
+ $filename = $string;
+ if (!exists($bb->{$function}->{$filename})) {
+ $bb->{$function}->{$filename} = [];
+ }
next;
}
# Got an actual line number
@@ -2691,21 +3139,20 @@ sub read_bbg_lines_record(*$$$$$$)
}
#
-# read_bbg(filename, base_dir)
+# read_bbg(filename)
#
# 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.
+# 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($$)
+sub read_bbg($)
{
- my ($bbg_filename, $base) = @_;
+ my ($bbg_filename) = @_;
my $file_magic = 0x67626267;
my $tag_function = 0x01000000;
my $tag_lines = 0x01450000;
@@ -2720,7 +3167,7 @@ sub read_bbg($$)
my $graph;
local *HANDLE;
- open(HANDLE, "<$bbg_filename") or goto open_error;
+ open(HANDLE, "<", $bbg_filename) or goto open_error;
binmode(HANDLE);
# Read magic
$word = read_bbg_value(*HANDLE, "file magic");
@@ -2752,7 +3199,7 @@ sub read_bbg($$)
# Read lines record
$filename = read_bbg_lines_record(HANDLE, $bbg_filename,
$bb, $fileorder, $filename,
- $function, $base);
+ $function);
goto incomplete if (!defined($filename));
} else {
# Skip record contents
@@ -2778,31 +3225,33 @@ magic_error:
}
#
-# read_gcno_word(handle[, description])
+# read_gcno_word(handle[, description, peek])
#
# Read and return a word in .gcno format.
#
-sub read_gcno_word(*;$)
+sub read_gcno_word(*;$$)
{
- my ($handle, $desc) = @_;
+ my ($handle, $desc, $peek) = @_;
- return graph_read($handle, 4, $desc);
+ return graph_read($handle, 4, $desc, $peek);
}
#
-# read_gcno_value(handle, big_endian[, description])
+# read_gcno_value(handle, big_endian[, description, peek])
#
# Read a word in .gcno format from handle and return its integer value
-# according to the specified endianness.
+# according to the specified endianness. If PEEK is non-zero, reset file
+# position after read.
#
-sub read_gcno_value(*$;$)
+sub read_gcno_value(*$;$$)
{
- my ($handle, $big_endian, $desc) = @_;
+ my ($handle, $big_endian, $desc, $peek) = @_;
my $word;
+ my $pos;
- $word = read_gcno_word($handle, $desc);
+ $word = read_gcno_word($handle, $desc, $peek);
return undef if (!defined($word));
if ($big_endian) {
return unpack("N", $word);
@@ -2841,16 +3290,16 @@ sub read_gcno_string(*$)
#
# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename,
-# function, base, big_endian)
+# function, 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(*$$$$$$$)
+sub read_gcno_lines_record(*$$$$$$)
{
my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function,
- $base, $big_endian) = @_;
+ $big_endian) = @_;
my $string;
my $lineno;
@@ -2870,7 +3319,10 @@ sub read_gcno_lines_record(*$$$$$$$)
if ($string eq "") {
return $filename;
}
- $filename = solve_relative_path($base, $string);
+ $filename = $string;
+ if (!exists($bb->{$function}->{$filename})) {
+ $bb->{$function}->{$filename} = [];
+ }
next;
}
# Got an actual line number
@@ -2886,7 +3338,52 @@ sub read_gcno_lines_record(*$$$$$$$)
}
#
-# read_gcno_function_record(handle, graph, base, big_endian)
+# determine_gcno_split_crc(handle, big_endian, rec_length)
+#
+# Determine if HANDLE refers to a .gcno file with a split checksum function
+# record format. Return non-zero in case of split checksum format, zero
+# otherwise, undef in case of read error.
+#
+
+sub determine_gcno_split_crc($$$)
+{
+ my ($handle, $big_endian, $rec_length) = @_;
+ my $strlen;
+ my $overlong_string;
+
+ return 1 if ($gcov_version >= $GCOV_VERSION_4_7_0);
+ return 1 if (is_compat($COMPAT_MODE_SPLIT_CRC));
+
+ # Heuristic:
+ # Decide format based on contents of next word in record:
+ # - pre-gcc 4.7
+ # This is the function name length / 4 which should be
+ # less than the remaining record length
+ # - gcc 4.7
+ # This is a checksum, likely with high-order bits set,
+ # resulting in a large number
+ $strlen = read_gcno_value($handle, $big_endian, undef, 1);
+ return undef if (!defined($strlen));
+ $overlong_string = 1 if ($strlen * 4 >= $rec_length - 12);
+
+ if ($overlong_string) {
+ if (is_compat_auto($COMPAT_MODE_SPLIT_CRC)) {
+ info("Auto-detected compatibility mode for split ".
+ "checksum .gcno file format\n");
+
+ return 1;
+ } else {
+ # Sanity check
+ warn("Found overlong string in function record: ".
+ "try '--compat split_crc'\n");
+ }
+ }
+
+ return 0;
+}
+
+#
+# read_gcno_function_record(handle, graph, big_endian, rec_length)
#
# Read a gcno format function record from handle and add the relevant data
# to graph. Return (filename, function) on success, undef on error.
@@ -2894,7 +3391,7 @@ sub read_gcno_lines_record(*$$$$$$$)
sub read_gcno_function_record(*$$$$)
{
- my ($handle, $bb, $fileorder, $base, $big_endian) = @_;
+ my ($handle, $bb, $fileorder, $big_endian, $rec_length) = @_;
my $filename;
my $function;
my $lineno;
@@ -2903,6 +3400,14 @@ sub read_gcno_function_record(*$$$$)
graph_expect("function record");
# Skip ident and checksum
graph_skip($handle, 8, "function ident and checksum") or return undef;
+ # Determine if this is a function record with split checksums
+ if (!defined($gcno_split_crc)) {
+ $gcno_split_crc = determine_gcno_split_crc($handle, $big_endian,
+ $rec_length);
+ return undef if (!defined($gcno_split_crc));
+ }
+ # Skip cfg checksum word in case of split checksums
+ graph_skip($handle, 4, "function cfg checksum") if ($gcno_split_crc);
# Read function name
graph_expect("function name");
$function = read_gcno_string($handle, $big_endian);
@@ -2911,7 +3416,6 @@ sub read_gcno_function_record(*$$$$)
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));
@@ -2923,7 +3427,7 @@ sub read_gcno_function_record(*$$$$)
}
#
-# read_gcno(filename, base_dir)
+# read_gcno(filename)
#
# Read the contents of the specified .gcno file and return the following
# mapping:
@@ -2931,14 +3435,13 @@ sub read_gcno_function_record(*$$$$)
# 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.
+# See the gcov-io.h file in the gcc 3.3 source code for a description of
+# the .gcno format.
#
-sub read_gcno($$)
+sub read_gcno($)
{
- my ($gcno_filename, $base) = @_;
+ my ($gcno_filename) = @_;
my $file_magic = 0x67636e6f;
my $tag_function = 0x01000000;
my $tag_lines = 0x01450000;
@@ -2952,9 +3455,11 @@ sub read_gcno($$)
my $fileorder = {};
my $instr;
my $graph;
+ my $filelength;
local *HANDLE;
- open(HANDLE, "<$gcno_filename") or goto open_error;
+ open(HANDLE, "<", $gcno_filename) or goto open_error;
+ $filelength = (stat(HANDLE))[7];
binmode(HANDLE);
# Read magic
$word = read_gcno_word(*HANDLE, "file magic");
@@ -2986,16 +3491,25 @@ sub read_gcno($$)
$next_pos = tell(HANDLE);
goto tell_error if ($next_pos == -1);
$next_pos += $length;
+ # Catch garbage at the end of a gcno file
+ if ($next_pos > $filelength) {
+ debug("Overlong record: file_length=$filelength ".
+ "rec_length=$length\n");
+ warn("WARNING: $gcno_filename: Overlong record at end ".
+ "of file!\n");
+ last;
+ }
# Process record
if ($tag == $tag_function) {
($filename, $function) = read_gcno_function_record(
- *HANDLE, $bb, $fileorder, $base, $big_endian);
+ *HANDLE, $bb, $fileorder, $big_endian,
+ $length);
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,
+ $filename, $function,
$big_endian);
goto incomplete if (!defined($filename));
} else {
@@ -3053,16 +3567,236 @@ sub get_gcov_capabilities()
{
my $help = `$gcov_tool --help`;
my %capabilities;
+ my %short_option_translations = (
+ 'a' => 'all-blocks',
+ 'b' => 'branch-probabilities',
+ 'c' => 'branch-counts',
+ 'f' => 'function-summaries',
+ 'h' => 'help',
+ 'l' => 'long-file-names',
+ 'n' => 'no-output',
+ 'o' => 'object-directory',
+ 'p' => 'preserve-paths',
+ 'u' => 'unconditional-branches',
+ 'v' => 'version',
+ );
foreach (split(/\n/, $help)) {
- next if (!/--(\S+)/);
- next if ($1 eq 'help');
- next if ($1 eq 'version');
- next if ($1 eq 'object-directory');
+ my $capability;
+ if (/--(\S+)/) {
+ $capability = $1;
+ } else {
+ # If the line provides a short option, translate it.
+ next if (!/^\s*-(\S)\s/);
+ $capability = $short_option_translations{$1};
+ next if not defined($capability);
+ }
+ next if ($capability eq 'help');
+ next if ($capability eq 'version');
+ next if ($capability eq 'object-directory');
- $capabilities{$1} = 1;
- debug("gcov has capability '$1'\n");
+ $capabilities{$capability} = 1;
+ debug("gcov has capability '$capability'\n");
}
return \%capabilities;
}
+
+#
+# parse_ignore_errors(@ignore_errors)
+#
+# Parse user input about which errors to ignore.
+#
+
+sub parse_ignore_errors(@)
+{
+ my (@ignore_errors) = @_;
+ my @items;
+ my $item;
+
+ return if (!@ignore_errors);
+
+ foreach $item (@ignore_errors) {
+ $item =~ s/\s//g;
+ if ($item =~ /,/) {
+ # Split and add comma-separated parameters
+ push(@items, split(/,/, $item));
+ } else {
+ # Add single parameter
+ push(@items, $item);
+ }
+ }
+ foreach $item (@items) {
+ my $item_id = $ERROR_ID{lc($item)};
+
+ if (!defined($item_id)) {
+ die("ERROR: unknown argument for --ignore-errors: ".
+ "$item\n");
+ }
+ $ignore[$item_id] = 1;
+ }
+}
+
+#
+# is_external(filename)
+#
+# Determine if a file is located outside of the specified data directories.
+#
+
+sub is_external($)
+{
+ my ($filename) = @_;
+ my $dir;
+
+ foreach $dir (@internal_dirs) {
+ return 0 if ($filename =~ /^\Q$dir\/\E/);
+ }
+ return 1;
+}
+
+#
+# compat_name(mode)
+#
+# Return the name of compatibility mode MODE.
+#
+
+sub compat_name($)
+{
+ my ($mode) = @_;
+ my $name = $COMPAT_MODE_TO_NAME{$mode};
+
+ return $name if (defined($name));
+
+ return "<unknown>";
+}
+
+#
+# parse_compat_modes(opt)
+#
+# Determine compatibility mode settings.
+#
+
+sub parse_compat_modes($)
+{
+ my ($opt) = @_;
+ my @opt_list;
+ my %specified;
+
+ # Initialize with defaults
+ %compat_value = %COMPAT_MODE_DEFAULTS;
+
+ # Add old style specifications
+ if (defined($opt_compat_libtool)) {
+ $compat_value{$COMPAT_MODE_LIBTOOL} =
+ $opt_compat_libtool ? $COMPAT_VALUE_ON
+ : $COMPAT_VALUE_OFF;
+ }
+
+ # Parse settings
+ if (defined($opt)) {
+ @opt_list = split(/\s*,\s*/, $opt);
+ }
+ foreach my $directive (@opt_list) {
+ my ($mode, $value);
+
+ # Either
+ # mode=off|on|auto or
+ # mode (implies on)
+ if ($directive !~ /^(\w+)=(\w+)$/ &&
+ $directive !~ /^(\w+)$/) {
+ die("ERROR: Unknown compatibility mode specification: ".
+ "$directive!\n");
+ }
+ # Determine mode
+ $mode = $COMPAT_NAME_TO_MODE{lc($1)};
+ if (!defined($mode)) {
+ die("ERROR: Unknown compatibility mode '$1'!\n");
+ }
+ $specified{$mode} = 1;
+ # Determine value
+ if (defined($2)) {
+ $value = $COMPAT_NAME_TO_VALUE{lc($2)};
+ if (!defined($value)) {
+ die("ERROR: Unknown compatibility mode ".
+ "value '$2'!\n");
+ }
+ } else {
+ $value = $COMPAT_VALUE_ON;
+ }
+ $compat_value{$mode} = $value;
+ }
+ # Perform auto-detection
+ foreach my $mode (sort(keys(%compat_value))) {
+ my $value = $compat_value{$mode};
+ my $is_autodetect = "";
+ my $name = compat_name($mode);
+
+ if ($value == $COMPAT_VALUE_AUTO) {
+ my $autodetect = $COMPAT_MODE_AUTO{$mode};
+
+ if (!defined($autodetect)) {
+ die("ERROR: No auto-detection for ".
+ "mode '$name' available!\n");
+ }
+
+ if (ref($autodetect) eq "CODE") {
+ $value = &$autodetect();
+ $compat_value{$mode} = $value;
+ $is_autodetect = " (auto-detected)";
+ }
+ }
+
+ if ($specified{$mode}) {
+ if ($value == $COMPAT_VALUE_ON) {
+ info("Enabling compatibility mode ".
+ "'$name'$is_autodetect\n");
+ } elsif ($value == $COMPAT_VALUE_OFF) {
+ info("Disabling compatibility mode ".
+ "'$name'$is_autodetect\n");
+ } else {
+ info("Using delayed auto-detection for ".
+ "compatibility mode ".
+ "'$name'\n");
+ }
+ }
+ }
+}
+
+sub compat_hammer_autodetect()
+{
+ if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 ||
+ $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302)
+ {
+ info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n");
+ return $COMPAT_VALUE_ON;
+ }
+ return $COMPAT_VALUE_OFF;
+}
+
+#
+# is_compat(mode)
+#
+# Return non-zero if compatibility mode MODE is enabled.
+#
+
+sub is_compat($)
+{
+ my ($mode) = @_;
+
+ return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON);
+ return 0;
+}
+
+#
+# is_compat_auto(mode)
+#
+# Return non-zero if compatibility mode MODE is set to auto-detect.
+#
+
+sub is_compat_auto($)
+{
+ my ($mode) = @_;
+
+ return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO);
+ return 0;
+}