diff options
| -rwxr-xr-x | 3rdParty/LCov/gendesc | 11 | ||||
| -rwxr-xr-x | 3rdParty/LCov/genhtml | 672 | ||||
| -rwxr-xr-x | 3rdParty/LCov/geninfo | 1200 | ||||
| -rwxr-xr-x | 3rdParty/LCov/genpng | 20 | ||||
| -rwxr-xr-x | 3rdParty/LCov/lcov | 489 | 
5 files changed, 1901 insertions, 491 deletions
| diff --git a/3rdParty/LCov/gendesc b/3rdParty/LCov/gendesc index 522ef69..7287c83 100755 --- a/3rdParty/LCov/gendesc +++ b/3rdParty/LCov/gendesc @@ -38,10 +38,12 @@  use strict;  use File::Basename;   use Getopt::Long; +use Cwd qw/abs_path/;  # 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 $tool_name		= basename($0); @@ -67,9 +69,6 @@ our $input_filename;  $SIG{__WARN__} = \&warn_handler;  $SIG{__DIE__} = \&die_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; -  # Parse command line options  if (!GetOptions("output-filename=s" => \$output_filename,  		"version" =>\$version, @@ -153,13 +152,13 @@ sub gen_desc()  	local *OUTPUT_HANDLE;  	my $empty_line = "ignore"; -	open(INPUT_HANDLE, $input_filename) +	open(INPUT_HANDLE, "<", $input_filename)  		or die("ERROR: cannot open $input_filename!\n");  	# Open output file for writing  	if ($output_filename)  	{ -		open(OUTPUT_HANDLE, ">$output_filename") +		open(OUTPUT_HANDLE, ">", $output_filename)  			or die("ERROR: cannot create $output_filename!\n");  	}  	else diff --git a/3rdParty/LCov/genhtml b/3rdParty/LCov/genhtml index d74063a..cf1b7f5 100755 --- a/3rdParty/LCov/genhtml +++ b/3rdParty/LCov/genhtml @@ -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 @@ -65,17 +65,23 @@  #  use strict; -use File::Basename;  +use File::Basename; +use File::Temp qw(tempfile);  use Getopt::Long;  use Digest::MD5 qw(md5_base64); +use Cwd qw/abs_path/;  # Global constants  our $title		= "LCOV - code coverage report"; -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 $tool_name		= basename($0); +# Specify coverage rate default precision +our $default_precision = 1; +  # Specify coverage rate limits (in %) for classifying file entries  # HI:   $hi_limit <= rate <= 100          graph color: green  # MED: $med_limit <= rate <  $hi_limit    graph color: orange @@ -145,6 +151,7 @@ our $BR_BRANCH		= 1;  our $BR_TAKEN		= 2;  our $BR_VEC_ENTRIES	= 3;  our $BR_VEC_WIDTH	= 32; +our $BR_VEC_MAX		= vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH);  # Additional offsets used when converting branch coverage data to HTML  our $BR_LEN	= 3; @@ -155,6 +162,12 @@ our $BR_CLOSE	= 5;  our $BR_SUB = 0;  our $BR_ADD = 1; +# Error classes which users may specify to ignore during processing +our $ERROR_SOURCE	= 0; +our %ERROR_ID = ( +	"source" => $ERROR_SOURCE, +); +  # Data related prototypes  sub print_usage(*);  sub gen_html(); @@ -165,7 +178,7 @@ sub info(@);  sub read_info_file($);  sub get_info_entry($);  sub set_info_entry($$$$$$$$$;$$$$$$); -sub get_prefix(@); +sub get_prefix($@);  sub shorten_prefix($);  sub get_dir_list(@);  sub get_relative_base_path($); @@ -181,7 +194,7 @@ sub get_affecting_tests($$$);  sub combine_info_files($$);  sub merge_checksums($$$);  sub combine_info_entries($$$); -sub apply_prefix($$); +sub apply_prefix($@);  sub system_no_output($@);  sub read_config($);  sub apply_config($); @@ -198,6 +211,9 @@ sub combine_brcount($$$);  sub get_br_found_and_hit($);  sub warn_handler($);  sub die_handler($); +sub parse_ignore_errors(@); +sub parse_dir_prefix(@); +sub rate($$;$$$);  # HTML related prototypes @@ -244,7 +260,8 @@ sub gen_png($$$@);  # Global variables & initialization  our %info_data;		# Hash containing all data from .info file -our $dir_prefix;	# Prefix to remove from all sub directories +our @opt_dir_prefix;	# Array of prefixes to remove from all sub directories +our @dir_prefix;  our %test_description;	# Hash containing test descriptions if available  our $date = get_date_string(); @@ -259,9 +276,9 @@ our $help;		# Help option flag  our $version;		# Version option flag  our $show_details;	# If set, generate detailed directory view  our $no_prefix;		# If set, do not remove filename prefix -our $func_coverage = 1;	# If set, generate function coverage statistics +our $func_coverage;	# If set, generate function coverage statistics  our $no_func_coverage;	# Disable func_coverage -our $br_coverage = 1;	# If set, generate branch coverage statistics +our $br_coverage;	# If set, generate branch coverage statistics  our $no_br_coverage;	# Disable br_coverage  our $sort = 1;		# If set, provide directory listings with sorted entries  our $no_sort;		# Disable sort @@ -279,15 +296,22 @@ our $html_epilog;	# Actual HTML epilog  our $html_ext = "html";	# Extension for generated HTML files  our $html_gzip = 0;	# Compress with gzip  our $demangle_cpp = 0;	# Demangle C++ function names +our @opt_ignore_errors;	# Ignore certain error classes during processing +our @ignore; +our $opt_config_file;	# User-specified configuration file location +our %opt_rc; +our $charset = "UTF-8";	# Default charset for HTML pages  our @fileview_sortlist;  our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b");  our @funcview_sortlist;  our @rate_name = ("Lo", "Med", "Hi");  our @rate_png = ("ruby.png", "amber.png", "emerald.png"); +our $lcov_func_coverage = 1; +our $lcov_branch_coverage = 0; +our $rc_desc_html = 0;	# lcovrc: genhtml_desc_html  our $cwd = `pwd`;	# Current working directory  chomp($cwd); -our $tool_dir = dirname($0);	# Directory where genhtml tool is installed  # @@ -297,17 +321,29 @@ our $tool_dir = dirname($0);	# Directory where genhtml tool is installed  $SIG{__WARN__} = \&warn_handler;  $SIG{__DIE__} = \&die_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; +# 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"); -# Add current working directory if $tool_dir is not already an absolute path -if (! ($tool_dir =~ /^\/(.*)$/))  { -	$tool_dir = "$cwd/$tool_dir"; +	# 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");  } @@ -316,9 +352,9 @@ 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({  		"genhtml_css_file"		=> \$css_filename,  		"genhtml_hi_limit"		=> \$hi_limit, @@ -337,6 +373,7 @@ if ($config)  		"genhtml_html_epilog"		=> \$html_epilog_file,  		"genhtml_html_extension"	=> \$html_ext,  		"genhtml_html_gzip"		=> \$html_gzip, +		"genhtml_precision"		=> \$default_precision,  		"genhtml_function_hi_limit"	=> \$fn_hi_limit,  		"genhtml_function_med_limit"	=> \$fn_med_limit,  		"genhtml_function_coverage"	=> \$func_coverage, @@ -345,14 +382,20 @@ if ($config)  		"genhtml_branch_coverage"	=> \$br_coverage,  		"genhtml_branch_field_width"	=> \$br_field_width,  		"genhtml_sort"			=> \$sort, +		"genhtml_charset"		=> \$charset, +		"genhtml_desc_html"		=> \$rc_desc_html, +		"lcov_function_coverage"	=> \$lcov_func_coverage, +		"lcov_branch_coverage"		=> \$lcov_branch_coverage,  		});  } -# Copy limit values if not specified +# Copy related values if not specified  $fn_hi_limit	= $hi_limit if (!defined($fn_hi_limit));  $fn_med_limit	= $med_limit if (!defined($fn_med_limit));  $br_hi_limit	= $hi_limit if (!defined($br_hi_limit));  $br_med_limit	= $med_limit if (!defined($br_med_limit)); +$func_coverage	= $lcov_func_coverage if (!defined($func_coverage)); +$br_coverage	= $lcov_branch_coverage if (!defined($br_coverage));  # Parse command line options  if (!GetOptions("output-directory|o=s"	=> \$output_directory, @@ -361,7 +404,7 @@ if (!GetOptions("output-directory|o=s"	=> \$output_directory,  		"keep-descriptions|k"	=> \$keep_descriptions,  		"css-file|c=s"		=> \$css_filename,  		"baseline-file|b=s"	=> \$base_filename, -		"prefix|p=s"		=> \$dir_prefix, +		"prefix|p=s"		=> \@opt_dir_prefix,  		"num-spaces=i"		=> \$tab_size,  		"no-prefix"		=> \$no_prefix,  		"no-sourceview"		=> \$no_sourceview, @@ -383,6 +426,10 @@ if (!GetOptions("output-directory|o=s"	=> \$output_directory,  		"sort"			=> \$sort,  		"no-sort"		=> \$no_sort,  		"demangle-cpp"		=> \$demangle_cpp, +		"ignore-errors=s"	=> \@opt_ignore_errors, +		"config-file=s"		=> \$opt_config_file, +		"rc=s%"			=> \%opt_rc, +		"precision=i"		=> \$default_precision,  		))  {  	print(STDERR "Use $tool_name --help to get usage information\n"); @@ -418,6 +465,12 @@ if ($version)  	exit(0);  } +# Determine which errors the user wants us to ignore +parse_ignore_errors(@opt_ignore_errors); + +# Split the list of prefixes if needed +parse_dir_prefix(@opt_dir_prefix); +  # Check for info filename  if (!@info_filenames)  { @@ -471,11 +524,11 @@ if ($no_sourceview && defined($frames))  }  # Issue a warning if --no-prefix is enabled together with --prefix -if ($no_prefix && defined($dir_prefix)) +if ($no_prefix && @dir_prefix)  {  	warn("WARNING: option --prefix disabled because --no-prefix was ".  	     "specified!\n"); -	$dir_prefix = undef; +	@dir_prefix = undef;  }  @fileview_sortlist = ($SORT_FILE); @@ -503,6 +556,13 @@ if ($demangle_cpp)  	}  } +# Make sure precision is within valid range +if ($default_precision < 1 || $default_precision > 4) +{ +	die("ERROR: specified precision is out of range (1 to 4)\n"); +} + +  # Make sure output_directory exists, create it if necessary  if ($output_directory)  { @@ -541,6 +601,9 @@ Misc:    -h, --help                        Print this help, then exit    -v, --version                     Print version number, then exit    -q, --quiet                       Do not print progress messages +      --config-file FILENAME        Specify configuration file location +      --rc SETTING=VALUE            Override configuration file setting +      --ignore-errors ERRORS        Continue after ERRORS (source)  Operation:    -o, --output-directory OUTDIR     Write HTML output to OUTDIR @@ -567,6 +630,7 @@ HTML output:        --html-gzip                   Use gzip to compress HTML        --(no-)sort                   Enable (disable) sorted coverage views        --demangle-cpp                Demangle C++ function names +      --precision NUM               Set precision of coverage rate  For more information see: $lcov_url  END_OF_USAGE @@ -607,8 +671,7 @@ sub get_overall_line($$$$)  	return "no data found" if (!defined($found) || $found == 0);  	$name = ($found == 1) ? $name_sn : $name_pl; -	return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit, -		       $found, $name); +	return rate($hit, $found, "% ($hit of $found $name)");  } @@ -636,6 +699,116 @@ sub print_overall_rate($$$$$$$$$)  		if ($br_do);  } +sub get_fn_list($) +{ +	my ($info) = @_; +	my %fns; +	my @result; + +	foreach my $filename (keys(%{$info})) { +		my $data = $info->{$filename}; +		my $funcdata = $data->{"func"}; +		my $sumfnccount = $data->{"sumfnc"}; + +		if (defined($funcdata)) { +			foreach my $func_name (keys(%{$funcdata})) { +				$fns{$func_name} = 1; +			} +		} + +		if (defined($sumfnccount)) { +			foreach my $func_name (keys(%{$sumfnccount})) { +				$fns{$func_name} = 1; +			} +		} +	} + +	@result = keys(%fns); + +	return \@result; +} + +# +# rename_functions(info, conv) +# +# Rename all function names in INFO according to CONV: OLD_NAME -> NEW_NAME. +# In case two functions demangle to the same name, assume that they are +# different object code implementations for the same source function. +# + +sub rename_functions($$) +{ +	my ($info, $conv) = @_; + +	foreach my $filename (keys(%{$info})) { +		my $data = $info->{$filename}; +		my $funcdata; +		my $testfncdata; +		my $sumfnccount; +		my %newfuncdata; +		my %newsumfnccount; +		my $f_found; +		my $f_hit; + +		# funcdata: function name -> line number +		$funcdata = $data->{"func"}; +		foreach my $fn (keys(%{$funcdata})) { +			my $cn = $conv->{$fn}; + +			# Abort if two functions on different lines map to the +			# same demangled name. +			if (defined($newfuncdata{$cn}) && +			    $newfuncdata{$cn} != $funcdata->{$fn}) { +				die("ERROR: Demangled function name $cn ". +				    "maps to different lines (". +				    $newfuncdata{$cn}." vs ". +				    $funcdata->{$fn}.") in $filename\n"); +			} +			$newfuncdata{$cn} = $funcdata->{$fn}; +		} +		$data->{"func"} = \%newfuncdata; + +		# testfncdata: test name -> testfnccount +		# testfnccount: function name -> execution count +		$testfncdata = $data->{"testfnc"}; +		foreach my $tn (keys(%{$testfncdata})) { +			my $testfnccount = $testfncdata->{$tn}; +			my %newtestfnccount; + +			foreach my $fn (keys(%{$testfnccount})) { +				my $cn = $conv->{$fn}; + +				# Add counts for different functions that map +				# to the same name. +				$newtestfnccount{$cn} += +					$testfnccount->{$fn}; +			} +			$testfncdata->{$tn} = \%newtestfnccount; +		} + +		# sumfnccount: function name -> execution count +		$sumfnccount = $data->{"sumfnc"}; +		foreach my $fn (keys(%{$sumfnccount})) { +			my $cn = $conv->{$fn}; + +			# Add counts for different functions that map +			# to the same name. +			$newsumfnccount{$cn} += $sumfnccount->{$fn}; +		} +		$data->{"sumfnc"} = \%newsumfnccount; + +		# Update function found and hit counts since they may have +		# changed +		$f_found = 0; +		$f_hit = 0; +		foreach my $fn (keys(%newsumfnccount)) { +			$f_found++; +			$f_hit++ if ($newsumfnccount{$fn} > 0); +		} +		$data->{"f_found"} = $f_found; +		$data->{"f_hit"} = $f_hit; +	} +}  #  # gen_html() @@ -701,14 +874,16 @@ sub gen_html()  		# User requested that we leave filenames alone  		info("User asked not to remove filename prefix\n");  	} -	elsif (!defined($dir_prefix)) +	elsif (! @dir_prefix)  	{  		# Get prefix common to most directories in list -		$dir_prefix = get_prefix(@dir_list); +		my $prefix = get_prefix(1, keys(%info_data)); -		if ($dir_prefix) +		if ($prefix)  		{ -			info("Found common filename prefix \"$dir_prefix\"\n"); +			info("Found common filename prefix \"$prefix\"\n"); +			$dir_prefix[0] = $prefix; +  		}  		else  		{ @@ -718,10 +893,17 @@ sub gen_html()  	}  	else  	{ -		info("Using user-specified filename prefix \"". -		     "$dir_prefix\"\n"); +		my $msg = "Using user-specified filename prefix "; +		for my $i (0 .. $#dir_prefix) +		{ +				$dir_prefix[$i] =~ s/\/+$//; +				$msg .= ", " unless 0 == $i; +				$msg .= "\"" . $dir_prefix[$i] . "\""; +		} +		info($msg . "\n");  	} +  	# Read in test description file if specified  	if ($desc_filename)  	{ @@ -763,11 +945,14 @@ sub gen_html()  		 $br_found, $br_hit)  			= process_dir($dir_name); +		# Handle files in root directory gracefully +		$dir_name = "root" if ($dir_name eq ""); +  		# Remove prefix if applicable -		if (!$no_prefix && $dir_prefix) +		if (!$no_prefix && @dir_prefix)  		{ -			# Match directory names beginning with $dir_prefix -			$dir_name = apply_prefix($dir_name, $dir_prefix); +			# Match directory names beginning with one of @dir_prefix +			$dir_name = apply_prefix($dir_name,@dir_prefix);  		}  		# Generate name for directory overview HTML page @@ -832,13 +1017,13 @@ sub html_create($$)  	if ($html_gzip)  	{ -		open($handle, "|gzip -c >$filename") +		open($handle, "|-", "gzip -c >'$filename'")  			or die("ERROR: cannot open $filename for writing ".  			       "(gzip)!\n");  	}  	else  	{ -		open($handle, ">$filename") +		open($handle, ">", $filename)  			or die("ERROR: cannot open $filename for writing!\n");  	}  } @@ -855,6 +1040,7 @@ sub write_dir_page($$$$$$$$$$$$$$$$$)  	if (!defined($trunc_dir)) {  		$trunc_dir = "";  	} +	$title .= " - " if ($trunc_dir ne "");  	write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");  	write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,  		     $overall_found, $overall_hit, $total_fn_found, @@ -904,8 +1090,8 @@ sub process_dir($)  	# Remove prefix if applicable  	if (!$no_prefix)  	{ -		# Match directory name beginning with $dir_prefix -		$rel_dir = apply_prefix($rel_dir, $dir_prefix); +		# Match directory name beginning with one of @dir_prefix +		$rel_dir = apply_prefix($rel_dir,@dir_prefix);  	}  	$trunc_dir = $rel_dir; @@ -916,6 +1102,10 @@ sub process_dir($)  		$rel_dir = substr($rel_dir, 1);  	} +	# Handle files in root directory gracefully +	$rel_dir = "root" if ($rel_dir eq ""); +	$trunc_dir = "root" if ($trunc_dir eq ""); +  	$base_dir = get_relative_base_path($rel_dir);  	create_sub_dir($rel_dir); @@ -1083,7 +1273,7 @@ sub write_function_page($$$$$$$$$$$$$$$$$$)  sub process_file($$$)  { -	info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n"); +	info("Processing file ".apply_prefix($_[2], @dir_prefix)."\n");  	my $trunc_dir = $_[0];  	my $rel_dir = $_[1]; @@ -1195,6 +1385,10 @@ sub process_file($$$)  #        "func"  -> \%funcdata  #        "found" -> $lines_found (number of instrumented lines found in file)  #	 "hit"   -> $lines_hit (number of executed lines in file) +#        "f_found" -> $fn_found (number of instrumented functions found in file) +#	 "f_hit"   -> $fn_hit (number of executed functions in file) +#        "b_found" -> $br_found (number of instrumented branches found in file) +#	 "b_hit"   -> $br_hit (number of executed branches in file)  #        "check" -> \%checkdata  #        "testfnc" -> \%testfncdata  #        "sumfnc"  -> \%sumfnccount @@ -1251,6 +1445,7 @@ sub read_info_file($)  	my $line_checksum;		# Checksum of current line  	my $br_found;  	my $br_hit; +	my $notified_about_relative_paths;  	local *INFO_HANDLE;		# Filehandle for .info file  	info("Reading data file $tracefile\n"); @@ -1281,14 +1476,14 @@ sub read_info_file($)  				"compressed file $_[0]!\n");  		# Open compressed file -		open(INFO_HANDLE, "gunzip -c $_[0]|") +		open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'")  			or die("ERROR: cannot start gunzip to decompress ".  			       "file $_[0]!\n");  	}  	else  	{  		# Open decompressed file -		open(INFO_HANDLE, $_[0]) +		open(INFO_HANDLE, "<", $_[0])  			or die("ERROR: cannot read file $_[0]!\n");  	} @@ -1317,7 +1512,16 @@ sub read_info_file($)  			{  				# Filename information found  				# Retrieve data for new entry -				$filename = $1; +				$filename = File::Spec->rel2abs($1, Cwd::cwd()); + +				if (!File::Spec->file_name_is_absolute($1) && +				    !$notified_about_relative_paths) +				{ +					info("Resolved relative source file ". +					     "path \"$1\" with CWD to ". +					     "\"$filename\".\n"); +					$notified_about_relative_paths = 1; +				}  				$data = $result{$filename};  				($testdata, $sumcount, $funcdata, $checkdata, @@ -1379,6 +1583,8 @@ sub read_info_file($)  			/^FN:(\d+),([^,]+)/ && do  			{ +				last if (!$func_coverage); +  				# Function data found, add to structure  				$funcdata->{$2} = $1; @@ -1397,6 +1603,7 @@ sub read_info_file($)  			/^FNDA:(\d+),([^,]+)/ && do  			{ +				last if (!$func_coverage);  				# Function call count found, add to structure  				# Add summary counts  				$sumfnccount->{$2} += $1; @@ -1414,6 +1621,7 @@ sub read_info_file($)  				my ($line, $block, $branch, $taken) =  				   ($1, $2, $3, $4); +				last if (!$br_coverage);  				$sumbrcount->{$line} =  					br_ivec_push($sumbrcount->{$line},  						     $block, $branch, $taken); @@ -1614,8 +1822,8 @@ sub set_info_entry($$$$$$$$$;$$$$$$)  sub add_counts($$)  { -	my %data1 = %{$_[0]};	# Hash 1 -	my %data2 = %{$_[1]};	# Hash 2 +	my $data1_ref = $_[0];	# Hash 1 +	my $data2_ref = $_[1];	# Hash 2  	my %result;		# Resulting hash  	my $line;		# Current line iteration scalar  	my $data1_count;	# Count of line in hash1 @@ -1623,10 +1831,10 @@ sub add_counts($$)  	my $found = 0;		# Total number of lines found  	my $hit = 0;		# Number of lines with a count > 0 -	foreach $line (keys(%data1)) +	foreach $line (keys(%$data1_ref))  	{ -		$data1_count = $data1{$line}; -		$data2_count = $data2{$line}; +		$data1_count = $data1_ref->{$line}; +		$data2_count = $data2_ref->{$line};  		# Add counts if present in both hashes  		if (defined($data2_count)) { $data1_count += $data2_count; } @@ -1638,14 +1846,14 @@ sub add_counts($$)  		if ($data1_count > 0) { $hit++; }  	} -	# Add lines unique to data2 -	foreach $line (keys(%data2)) +	# Add lines unique to data2_ref +	foreach $line (keys(%$data2_ref))  	{ -		# Skip lines already in data1 -		if (defined($data1{$line})) { next; } +		# Skip lines already in data1_ref +		if (defined($data1_ref->{$line})) { next; } -		# Copy count from data2 -		$result{$line} = $data2{$line}; +		# Copy count from data2_ref +		$result{$line} = $data2_ref->{$line};  		$found++;  		if ($result{$line} > 0) { $hit++; } @@ -2110,16 +2318,17 @@ sub combine_info_files($$)  # -# get_prefix(filename_list) +# get_prefix(min_dir, filename_list)  #  # Search FILENAME_LIST for a directory prefix which is common to as many  # list entries as possible, so that removing this prefix will minimize the -# sum of the lengths of all resulting shortened filenames. +# sum of the lengths of all resulting shortened filenames while observing +# that no filename has less than MIN_DIR parent directories.  # -sub get_prefix(@) +sub get_prefix($@)  { -	my @filename_list = @_;		# provided list of filenames +	my ($min_dir, @filename_list) = @_;  	my %prefix;			# mapping: prefix -> sum of lengths  	my $current;			# Temporary iteration variable @@ -2128,12 +2337,14 @@ sub get_prefix(@)  	{  		# Need explicit assignment to get a copy of $_ so that  		# shortening the contained prefix does not affect the list -		$current = shorten_prefix($_); +		$current = $_;  		while ($current = shorten_prefix($current))  		{ +			$current .= "/"; +  			# Skip rest if the remaining prefix has already been  			# added to hash -			if ($prefix{$current}) { last; } +			if (exists($prefix{$current})) { last; }  			# Initialize with 0  			$prefix{$current}="0"; @@ -2141,6 +2352,20 @@ sub get_prefix(@)  	} +	# Remove all prefixes that would cause filenames to have less than +	# the minimum number of parent directories +	foreach my $filename (@filename_list) { +		my $dir = dirname($filename); + +		for (my $i = 0; $i < $min_dir; $i++) { +			delete($prefix{$dir."/"}); +			$dir = shorten_prefix($dir); +		} +	} + +	# Check if any prefix remains +	return undef if (!%prefix); +  	# Calculate sum of lengths for all prefixes  	foreach $current (keys(%prefix))  	{ @@ -2169,6 +2394,8 @@ sub get_prefix(@)  		}  	} +	$current =~ s/\/$//; +  	return($current);  } @@ -2260,7 +2487,7 @@ sub read_testfile($)  	my $changed_testname;  	local *TEST_HANDLE; -	open(TEST_HANDLE, "<".$_[0]) +	open(TEST_HANDLE, "<", $_[0])  		or die("ERROR: cannot open $_[0]!\n");  	while (<TEST_HANDLE>) @@ -2281,6 +2508,9 @@ sub read_testfile($)  		# Match lines beginning with TD:<whitespace(s)>  		if (/^TD:\s+(.*?)\s*$/)  		{ +			if (!defined($test_name)) { +				die("ERROR: Found test description without prior test name in $_[0]:$.\n"); +			}  			# Check for empty line  			if ($1)  			{ @@ -2348,10 +2578,15 @@ sub get_date_string()  	my $year;  	my $month;  	my $day; +	my $hour; +	my $min; +	my $sec; -	($year, $month, $day) = (localtime())[5, 4, 3]; +	($year, $month, $day, $hour, $min, $sec) = +		(localtime())[5, 4, 3, 2, 1, 0]; -	return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day); +	return sprintf("%d-%02d-%02d %02d:%02d:%02d", $year+1900, $month+1, +		       $day, $hour, $min, $sec);  } @@ -2408,8 +2643,10 @@ sub write_description_file($$$$$$$)  	foreach $test_name (sort(keys(%description)))  	{ -		write_test_table_entry(*HTML_HANDLE, $test_name, -				       escape_html($description{$test_name})); +		my $desc = $description{$test_name}; + +		$desc = escape_html($desc) if (!$rc_desc_html); +		write_test_table_entry(*HTML_HANDLE, $test_name, $desc);  	}  	write_test_table_epilog(*HTML_HANDLE); @@ -2531,7 +2768,7 @@ sub write_png_files()  		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);  	foreach (keys(%data))  	{ -		open(PNG_HANDLE, ">".$_) +		open(PNG_HANDLE, ">", $_)  			or die("ERROR: cannot create $_!\n");  		binmode(PNG_HANDLE);  		print(PNG_HANDLE map(chr,@{$data{$_}})); @@ -2549,7 +2786,7 @@ sub write_htaccess_file()  	local *HTACCESS_HANDLE;  	my $htaccess_data; -	open(*HTACCESS_HANDLE, ">.htaccess") +	open(*HTACCESS_HANDLE, ">", ".htaccess")  		or die("ERROR: cannot open .htaccess for writing!\n");  	$htaccess_data = (<<"END_OF_HTACCESS") @@ -2582,7 +2819,7 @@ sub write_css_file()  		return;  	} -	open(CSS_HANDLE, ">gcov.css") +	open(CSS_HANDLE, ">", "gcov.css")  		or die ("ERROR: cannot open gcov.css for writing!\n"); @@ -3132,6 +3369,7 @@ END_OF_CSS  sub get_bar_graph_code($$$)  { +	my ($base_dir, $found, $hit) = @_;  	my $rate;  	my $alt;  	my $width; @@ -3142,13 +3380,12 @@ sub get_bar_graph_code($$$)  	# Check number of instrumented lines  	if ($_[1] == 0) { return ""; } -	$rate		= $_[2] * 100 / $_[1]; -	$alt		= sprintf("%.1f", $rate)."%"; -	$width		= sprintf("%.0f", $rate); -	$remainder	= sprintf("%d", 100-$width); +	$alt		= rate($hit, $found, "%"); +	$width		= rate($hit, $found, undef, 0); +	$remainder	= 100 - $width;  	# Decide which .png file to use -	$png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit, +	$png_name = $rate_png[classify_rate($found, $hit, $med_limit,  					    $hi_limit)];  	if ($width == 0) @@ -3197,7 +3434,7 @@ sub classify_rate($$$$)  	if ($found == 0) {  		return 2;  	} -	$rate = $hit * 100 / $found; +	$rate = rate($hit, $found);  	if ($rate < $med) {  		return 0;  	} elsif ($rate < $hi) { @@ -3415,12 +3652,13 @@ sub write_file_table_entry(*$$$@)  	my ($handle, $base_dir, $filename, $page_link, @entries) = @_;  	my $file_code;  	my $entry; +	my $esc_filename = escape_html($filename);  	# Add link to source if provided  	if (defined($page_link) && $page_link ne "") { -		$file_code = "<a href=\"$page_link\">$filename</a>"; +		$file_code = "<a href=\"$page_link\">$esc_filename</a>";  	} else { -		$file_code = $filename; +		$file_code = $esc_filename;  	}  	# First column: filename @@ -3450,7 +3688,7 @@ END_OF_HTML  			$rate = "-";  			$class = "Hi";  		} else { -			$rate = sprintf("%.1f %%", $hit * 100 / $found); +			$rate = rate($hit, $found, " %");  			$class = $rate_name[classify_rate($found, $hit,  					    $med, $hi)];  		} @@ -3490,11 +3728,8 @@ END_OF_HTML  	# Test data  	foreach $entry (@entries) {  		my ($found, $hit) = @{$entry}; -		my $rate = "-"; +		my $rate = rate($hit, $found, " %"); -		if ($found > 0) { -			$rate = sprintf("%.1f %%", $hit * 100 / $found); -		}  		write_html($handle, <<END_OF_HTML);  	      <td class="testPer">$rate</td>  	      <td class="testNum">$hit / $found</td> @@ -4010,7 +4245,7 @@ sub write_frameset(*$$$)  	<html lang="en">  	<head> -	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> +	  <meta http-equiv="Content-Type" content="text/html; charset=$charset">  	  <title>$_[3]</title>  	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">  	</head> @@ -4073,7 +4308,7 @@ sub write_overview(*$$$$)  	<head>  	  <title>$_[3]</title> -	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> +	  <meta http-equiv="Content-Type" content="text/html; charset=$charset">  	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">  	</head> @@ -4120,17 +4355,6 @@ END_OF_HTML  } -# format_rate(found, hit) -# -# Return formatted percent string for coverage rate. -# - -sub format_rate($$) -{	 -	return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %"; -} - -  sub max($$)  {  	my ($a, $b) = @_; @@ -4172,6 +4396,7 @@ sub write_header(*$$$$$$$$$$)  	my @row_right;  	my $num_rows;  	my $i; +	my $esc_trunc_name = escape_html($trunc_name);  	$base_name = basename($rel_filename); @@ -4187,12 +4412,14 @@ sub write_header(*$$$$$$$$$$)  		# Directory overview  		$base_dir = get_relative_base_path($rel_filename);  		$view = "<a href=\"$base_dir"."index.$html_ext\">". -			"$overview_title</a> - $trunc_name"; +			"$overview_title</a> - $esc_trunc_name";  	}  	elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC)  	{  		# File view  		my $dir_name = dirname($rel_filename); +		my $esc_base_name = escape_html($base_name); +		my $esc_dir_name = escape_html($dir_name);  		$base_dir = get_relative_base_path($dir_name);  		if ($frames) @@ -4202,21 +4429,25 @@ sub write_header(*$$$$$$$$$$)  			$view = "<a href=\"$base_dir"."index.$html_ext\" ".  				"target=\"_parent\">$overview_title</a> - ".  				"<a href=\"index.$html_ext\" target=\"_parent\">". -				"$dir_name</a> - $base_name"; +				"$esc_dir_name</a> - $esc_base_name";  		}  		else  		{  			$view = "<a href=\"$base_dir"."index.$html_ext\">".  				"$overview_title</a> - ".  				"<a href=\"index.$html_ext\">". -				"$dir_name</a> - $base_name"; +				"$esc_dir_name</a> - $esc_base_name";  		}  		# Add function suffix  		if ($func_coverage) {  			$view .= "<span style=\"font-size: 80%;\">";  			if ($type == $HDR_SOURCE) { -				$view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)"; +				if ($sort) { +					$view .= " (source / <a href=\"$base_name.func-sort-c.$html_ext\">functions</a>)"; +				} else { +					$view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)"; +				}  			} elsif ($type == $HDR_FUNC) {  				$view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";  			} @@ -4303,7 +4534,7 @@ END_OF_HTML  	# Line coverage  	$style = $rate_name[classify_rate($lines_found, $lines_hit,  					  $med_limit, $hi_limit)]; -	$rate = format_rate($lines_found, $lines_hit); +	$rate = rate($lines_hit, $lines_found, " %");  	push(@row_right, [[undef, "headerItem", "Lines:"],  			  [undef, "headerCovTableEntry", $lines_hit],  			  [undef, "headerCovTableEntry", $lines_found], @@ -4313,7 +4544,7 @@ END_OF_HTML  	if ($func_coverage) {  		$style = $rate_name[classify_rate($fn_found, $fn_hit,  						  $fn_med_limit, $fn_hi_limit)]; -		$rate = format_rate($fn_found, $fn_hit); +		$rate = rate($fn_hit, $fn_found, " %");  		push(@row_right, [[undef, "headerItem", "Functions:"],  				  [undef, "headerCovTableEntry", $fn_hit],  				  [undef, "headerCovTableEntry", $fn_found], @@ -4324,7 +4555,7 @@ END_OF_HTML  	if ($br_coverage) {  		$style = $rate_name[classify_rate($br_found, $br_hit,  						  $br_med_limit, $br_hi_limit)]; -		$rate = format_rate($br_found, $br_hit); +		$rate = rate($br_hit, $br_found, " %");  		push(@row_right, [[undef, "headerItem", "Branches:"],  				  [undef, "headerCovTableEntry", $br_hit],  				  [undef, "headerCovTableEntry", $br_found], @@ -4795,6 +5026,7 @@ sub br_ivec_get($$)  	# Retrieve data from vector  	$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); @@ -4820,10 +5052,12 @@ sub br_ivec_push($$$$)  	my $i;  	$vec = "" if (!defined($vec)); +	$block = $BR_VEC_MAX if $block < 0;  	# Check if branch already exists in vector  	for ($i = 0; $i < $num; $i++) {  		my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); +		$v_block = $BR_VEC_MAX if $v_block < 0;  		next if ($v_block != $block || $v_branch != $branch); @@ -4958,19 +5192,43 @@ sub write_source($$$$$$$)  	my $sumbrcount = $_[6];  	my $datafunc = get_hash_reverse($funcdata);  	my $add_anchor; +	my @file;  	if ($_[2])  	{  		%count_data = %{$_[2]};  	} -	open(SOURCE_HANDLE, "<".$source_filename) -		or die("ERROR: cannot open $source_filename for reading!\n"); +	if (!open(SOURCE_HANDLE, "<", $source_filename)) { +		my @lines; +		my $last_line = 0; + +		if (!$ignore[$ERROR_SOURCE]) { +			die("ERROR: cannot read $source_filename\n"); +		} + +		# Continue without source file +		warn("WARNING: cannot read $source_filename!\n"); + +		@lines = sort( { $a <=> $b }  keys(%count_data)); +		if (@lines) { +			$last_line = $lines[scalar(@lines) - 1]; +		} +		return ( ":" ) if ($last_line < 1); + +		# Simulate gcov behavior +		for ($line_number = 1; $line_number <= $last_line; +		     $line_number++) { +			push(@file, "/* EOF */"); +		} +	} else { +		@file = <SOURCE_HANDLE>; +	}  	write_source_prolog(*HTML_HANDLE); - -	for ($line_number = 1; <SOURCE_HANDLE> ; $line_number++) -	{ +	$line_number = 0; +	foreach (@file) { +		$line_number++;  		chomp($_);  		# Also remove CR from line-end @@ -5055,8 +5313,51 @@ sub funcview_get_sorted($$$)  	if ($type == 0) {  		return sort(keys(%{$funcdata}));  	} -	return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}} -		    keys(%{$sumfncdata})); +	return sort({ +		$sumfncdata->{$b} == $sumfncdata->{$a} ? +			$a cmp $b : $sumfncdata->{$a} <=> $sumfncdata->{$b} +		} keys(%{$sumfncdata})); +} + +sub demangle_list($) +{ +	my ($list) = @_; +	my $tmpfile; +	my $handle; +	my %demangle; +	my %versions; + +	# Write function names to file +	($handle, $tmpfile) = tempfile(); +	die("ERROR: could not create temporary file") if (!defined($tmpfile)); +	print($handle join("\n", @$list)); +	close($handle); + +	# Build translation hash from c++filt output +	open($handle, "-|", "c++filt < $tmpfile") or +		die("ERROR: could not run c++filt: $!\n"); +	foreach my $func (@$list) { +		my $translated = <$handle>; +		my $version; + +		last if (!defined($translated)); +		chomp($translated); + +		$version = ++$versions{$translated}; +		$translated .= ".$version" if ($version > 1); +		$demangle{$func} = $translated; +	} +	close($handle); + +	if (scalar(keys(%demangle)) != scalar(@$list)) { +		die("ERROR: c++filt output not as expected (". +		    scalar(keys(%demangle))." vs ".scalar(@$list).") lines\n"); +	} + +	unlink($tmpfile) or +		warn("WARNING: could not remove temporary file $tmpfile: $!\n"); + +	return \%demangle;  }  # @@ -5086,6 +5387,7 @@ sub write_function_table(*$$$$$$$$$$)  	my $func;  	my $func_code;  	my $count_code; +	my $demangle;  	# Get HTML code for headings  	$func_code = funcview_get_func_code($name, $base, $type); @@ -5100,7 +5402,12 @@ sub write_function_table(*$$$$$$$$$$)  	    </tr>  END_OF_HTML  	; -	 + +	# Get demangle translation hash +	if ($demangle_cpp) { +		$demangle = demangle_list([ sort(keys(%{$funcdata})) ]); +	} +  	# Get a sorted table  	foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {  		if (!defined($funcdata->{$func})) @@ -5113,12 +5420,10 @@ END_OF_HTML  		my $count = $sumfncdata->{$name};  		my $countstyle; -		# Demangle C++ function names if requested -		if ($demangle_cpp) { -			$name = `c++filt "$name"`; -			chomp($name); -		} -		# Escape any remaining special characters +		# Replace function name with demangled version if available +		$name = $demangle->{$name} if (exists($demangle->{$name})); + +		# Escape special characters  		$name = escape_html($name);  		if ($startline < 1) {  			$startline = 1; @@ -5412,22 +5717,25 @@ sub remove_unused_descriptions()  # -# apply_prefix(filename, prefix) +# apply_prefix(filename, PREFIXES)  # -# If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return -# resulting string, otherwise return FILENAME. +# If FILENAME begins with PREFIX from PREFIXES, remove PREFIX from FILENAME +# and return resulting string, otherwise return FILENAME.  # -sub apply_prefix($$) +sub apply_prefix($@)  { -	my $filename = $_[0]; -	my $prefix = $_[1]; +	my $filename = shift; +	my @dir_prefix = @_; -	if (defined($prefix) && ($prefix ne "")) +	if (@dir_prefix)  	{ -		if ($filename =~ /^\Q$prefix\E\/(.*)$/) +		foreach my $prefix (@dir_prefix)  		{ -			return substr($filename, length($prefix) + 1); +			if ($prefix ne "" && $filename =~ /^\Q$prefix\E\/(.*)$/) +			{ +				return substr($filename, length($prefix) + 1); +			}  		}  	} @@ -5455,12 +5763,12 @@ 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");  	system(@_);  	$result = $?; @@ -5470,8 +5778,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;  } @@ -5492,7 +5800,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; @@ -5531,8 +5839,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($) @@ -5541,8 +5849,9 @@ sub apply_config($)  	foreach (keys(%{$ref}))  	{ -		if (defined($config->{$_})) -		{ +		if (defined($opt_rc{$_})) { +			${$ref->{$_}} = $opt_rc{$_}; +		} elsif (defined($config->{$_})) {  			${$ref->{$_}} = $config->{$_};  		}  	} @@ -5565,7 +5874,7 @@ sub get_html_prolog($)  	{  		local *HANDLE; -		open(HANDLE, "<".$filename) +		open(HANDLE, "<", $filename)  			or die("ERROR: cannot open html prolog $filename!\n");  		while (<HANDLE>)  		{ @@ -5581,7 +5890,7 @@ sub get_html_prolog($)  <html lang="en">  <head> -  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> +  <meta http-equiv="Content-Type" content="text/html; charset=$charset">    <title>\@pagetitle\@</title>    <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css">  </head> @@ -5611,7 +5920,7 @@ sub get_html_epilog($)  	{  		local *HANDLE; -		open(HANDLE, "<".$filename) +		open(HANDLE, "<", $filename)  			or die("ERROR: cannot open html epilog $filename!\n");  		while (<HANDLE>)  		{ @@ -5646,3 +5955,96 @@ sub die_handler($)  	die("$tool_name: $msg");  } + +# +# 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; +	} +} + +# +# parse_dir_prefix(@dir_prefix) +# +# Parse user input about the prefix list +# + +sub parse_dir_prefix(@) +{ +	my (@opt_dir_prefix) = @_; +	my $item; + +	return if (!@opt_dir_prefix); + +	foreach $item (@opt_dir_prefix) { +		if ($item =~ /,/) { +			# Split and add comma-separated parameters +			push(@dir_prefix, split(/,/, $item)); +		} else { +			# Add single parameter +			push(@dir_prefix, $item); +		} +	} +} + +# +# rate(hit, found[, suffix, precision, width]) +# +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. +# PRECISION specifies the precision of the result. SUFFIX defines a +# string that is appended to the result if FOUND is non-zero. Spaces +# are added to the start of the resulting string until it is at least WIDTH +# characters wide. +# + +sub rate($$;$$$) +{ +        my ($hit, $found, $suffix, $precision, $width) = @_; +        my $rate;  + +	# Assign defaults if necessary +	$precision	= $default_precision if (!defined($precision)); +	$suffix		= ""	if (!defined($suffix)); +	$width		= 0	if (!defined($width)); +         +        return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); +        $rate = sprintf("%.*f", $precision, $hit * 100 / $found); + +	# Adjust rates if necessary +        if ($rate == 0 && $hit > 0) { +		$rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); +        } elsif ($rate == 100 && $hit != $found) { +		$rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); +	} + +	return sprintf("%*s", $width, $rate.$suffix); +} 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; +} diff --git a/3rdParty/LCov/genpng b/3rdParty/LCov/genpng index 7fe9dfe..55e013e 100755 --- a/3rdParty/LCov/genpng +++ b/3rdParty/LCov/genpng @@ -22,7 +22,7 @@  #   This script creates an overview PNG image of a source code file by  #   representing each source code character by a single pixel.  # -#   Note that the PERL module GD.pm is required for this script to work. +#   Note that the Perl module GD.pm is required for this script to work.  #   It may be obtained from http://www.cpan.org  #  # History: @@ -32,10 +32,12 @@  use strict;  use File::Basename;   use Getopt::Long; +use Cwd qw/abs_path/;  # 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 $tool_name		= basename($0); @@ -53,9 +55,6 @@ sub genpng_die_handler($);  # Code entry point  # -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; -  # Check whether required module GD.pm is installed  if (check_and_load_module("GD"))  { @@ -182,7 +181,7 @@ sub genpng_process_file($$$$)  	local *HANDLE;  	my @source; -	open(HANDLE, "<$filename") +	open(HANDLE, "<", $filename)  		or die("ERROR: cannot open $filename!\n");  	# Check for .gcov filename extension @@ -238,7 +237,7 @@ sub gen_png($$$@)  	my $overview_width = shift(@_);	# Imagewidth for image  	my $tab_size = shift(@_);	# Replacement string for tab signs  	my @source = @_;	# Source code as passed via argument 2 -	my $height = scalar(@source);	# Height as define by source size +	my $height;		# Height as define by source size  	my $overview;		# Source code overview image data  	my $col_plain_back;	# Color for overview background  	my $col_plain_text;	# Color for uninstrumented text @@ -261,6 +260,11 @@ sub gen_png($$$@)  	my $replacement;	# Replacement string for tabulator chars  	local *PNG_HANDLE;	# Handle for output PNG file +	# Handle empty source files +	if (!@source) { +		@source = ( "" ); +	} +	$height = scalar(@source);  	# Create image  	$overview = new GD::Image($overview_width, $height)  		or die("ERROR: cannot allocate overview image!\n"); @@ -362,7 +366,7 @@ sub gen_png($$$@)  	}  	# Write PNG file -	open (PNG_HANDLE, ">$filename") +	open (PNG_HANDLE, ">", $filename)  		or die("ERROR: cannot write png file $filename!\n");  	binmode(*PNG_HANDLE);  	print(PNG_HANDLE $overview->png()); diff --git a/3rdParty/LCov/lcov b/3rdParty/LCov/lcov index 4e392ff..7760ba2 100755 --- a/3rdParty/LCov/lcov +++ b/3rdParty/LCov/lcov @@ -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 @@ -71,7 +71,8 @@ use Cwd qw /abs_path getcwd/;  # Global 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 $tool_name		= basename($0); @@ -93,6 +94,7 @@ our $BR_BRANCH		= 1;  our $BR_TAKEN		= 2;  our $BR_VEC_ENTRIES	= 3;  our $BR_VEC_WIDTH	= 32; +our $BR_VEC_MAX		= vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH);  # Branch data combination types  our $BR_SUB = 0; @@ -139,6 +141,8 @@ sub lcov_geninfo(@);  sub create_package($$$;$);  sub get_func_found_and_hit($);  sub br_ivec_get($$); +sub summary(); +sub rate($$;$$$);  # Global variables & initialization  our @directory;		# Specifies where to get coverage data from @@ -168,7 +172,7 @@ our $no_checksum;	# If set, don't calculate a checksum for each line  our $compat_libtool;	# If set, indicates that libtool mode is to be enabled  our $no_compat_libtool;	# If set, indicates that libtool mode is to be disabled  our $gcov_tool; -our $ignore_errors; +our @opt_ignore_errors;  our $initial;  our $no_recursion = 0;  our $to_package; @@ -177,7 +181,6 @@ our $maxdepth;  our $no_markers;  our $config;		# Configuration file contents  chomp($cwd); -our $tool_dir = dirname($0);	# Directory where genhtml tool is installed  our @temp_dirs;  our $gcov_gkv;		# gcov kernel support version found on machine  our $opt_derive_func_data; @@ -186,12 +189,20 @@ our $opt_list_full_path;  our $opt_no_list_full_path;  our $opt_list_width = 80;  our $opt_list_truncate_max = 20; +our $opt_external; +our $opt_no_external; +our $opt_config_file; +our %opt_rc; +our @opt_summary; +our $opt_compat;  our $ln_overall_found;  our $ln_overall_hit;  our $fn_overall_found;  our $fn_overall_hit;  our $br_overall_found;  our $br_overall_hit; +our $func_coverage = 1; +our $br_coverage = 0;  # @@ -203,17 +214,29 @@ $SIG{__DIE__} = \&die_handler;  $SIG{'INT'} = \&abort_handler;  $SIG{'QUIT'} = \&abort_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; +# 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"); -# Add current working directory if $tool_dir is not already an absolute path -if (! ($tool_dir =~ /^\/(.*)$/))  { -	$tool_dir = "$cwd/$tool_dir"; +	# 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");  } @@ -222,15 +245,17 @@ 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({  		"lcov_gcov_dir"		=> \$gcov_dir,  		"lcov_tmp_dir"		=> \$tmp_dir,  		"lcov_list_full_path"	=> \$opt_list_full_path,  		"lcov_list_width"	=> \$opt_list_width,  		"lcov_list_truncate_max"=> \$opt_list_truncate_max, +		"lcov_branch_coverage"	=> \$br_coverage, +		"lcov_function_coverage"=> \$func_coverage,  	});  } @@ -259,7 +284,7 @@ if (!GetOptions("directory|d|di=s" => \@directory,  		"compat-libtool" => \$compat_libtool,  		"no-compat-libtool" => \$no_compat_libtool,  		"gcov-tool=s" => \$gcov_tool, -		"ignore-errors=s" => \$ignore_errors, +		"ignore-errors=s" => \@opt_ignore_errors,  		"initial|i" => \$initial,  		"no-recursion" => \$no_recursion,  		"to-package=s" => \$to_package, @@ -269,6 +294,12 @@ if (!GetOptions("directory|d|di=s" => \@directory,  		"debug" => \$opt_debug,  		"list-full-path" => \$opt_list_full_path,  		"no-list-full-path" => \$opt_no_list_full_path, +		"external" => \$opt_external, +		"no-external" => \$opt_no_external, +		"summary=s" => \@opt_summary, +		"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"); @@ -294,6 +325,11 @@ else  		$opt_list_full_path = ($opt_no_list_full_path ? 0 : 1);  		$opt_no_list_full_path = undef;  	} + +	if (defined($opt_no_external)) { +		$opt_external = 0; +		$opt_no_external = undef; +	}  }  # Check for help option @@ -341,7 +377,7 @@ else  check_options();  # Only --extract, --remove and --diff allow unnamed parameters -if (@ARGV && !($extract || $remove || $diff)) +if (@ARGV && !($extract || $remove || $diff || @opt_summary))  {  	die("Extra parameter found: '".join(" ", @ARGV)."'\n".  	    "Use $tool_name --help to get usage information\n"); @@ -429,6 +465,12 @@ elsif ($diff)  	 $fn_overall_found, $fn_overall_hit,  	 $br_overall_found, $br_overall_hit) = diff();  } +elsif (@opt_summary) +{ +	($ln_overall_found, $ln_overall_hit, +	 $fn_overall_found, $fn_overall_hit, +	 $br_overall_found, $br_overall_hit) = summary(); +}  temp_cleanup(); @@ -471,6 +513,7 @@ Operation:    -r, --remove FILE PATTERN       Remove files matching PATTERN from FILE    -l, --list FILE                 List contents of tracefile FILE        --diff FILE DIFF            Transform tracefile FILE according to DIFF +      --summary FILE              Show summary coverage data for tracefiles  Options:    -i, --initial                   Capture initial zero coverage data @@ -493,6 +536,10 @@ Options:        --no-markers                Ignore exclusion markers in source code        --derive-func-data          Generate function data from line data        --list-full-path            Print full path during a list operation +      --(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 @@ -518,17 +565,18 @@ sub check_options()  	$remove && $i++;  	$list && $i++;  	$diff && $i++; +	@opt_summary && $i++;  	if ($i == 0)  	{ -		die("Need one of the options -z, -c, -a, -e, -r, -l or ". -		    "--diff\n". +		die("Need one of options -z, -c, -a, -e, -r, -l, ". +		    "--diff or --summary\n".  		    "Use $tool_name --help to get usage information\n");  	}  	elsif ($i > 1)  	{ -		die("ERROR: only one of -z, -c, -a, -e, -r, -l or ". -		    "--diff allowed!\n". +		die("ERROR: only one of -z, -c, -a, -e, -r, -l, ". +		    "--diff or --summary allowed!\n".  		    "Use $tool_name --help to get usage information\n");  	}  } @@ -551,7 +599,7 @@ sub userspace_reset()  	{  		info("Deleting all .da files in $current_dir".  		     ($no_recursion?"\n":" and subdirectories\n")); -		@file_list = `find "$current_dir" $maxdepth $follow -name \\*\\.da -o -name \\*\\.gcda -type f 2>/dev/null`; +		@file_list = `find "$current_dir" $maxdepth $follow -name \\*\\.da -type f -o -name \\*\\.gcda -type f 2>/dev/null`;  		chomp(@file_list);  		foreach (@file_list)  		{ @@ -613,7 +661,7 @@ sub kernel_reset()  	} else {  		die("ERROR: no reset control found in $gcov_dir\n");  	} -	open(HANDLE, ">$reset_file") or +	open(HANDLE, ">", $reset_file) or  		die("ERROR: cannot write to $reset_file!\n");  	print(HANDLE "0");  	close(HANDLE); @@ -635,10 +683,10 @@ sub lcov_copy_single($$)  	local $/;  	local *HANDLE; -	open(HANDLE, "<$from") or die("ERROR: cannot read $from: $!\n"); +	open(HANDLE, "<", $from) or die("ERROR: cannot read $from: $!\n");  	$content = <HANDLE>;  	close(HANDLE); -	open(HANDLE, ">$to") or die("ERROR: cannot write $from: $!\n"); +	open(HANDLE, ">", $to) or die("ERROR: cannot write $from: $!\n");  	if (defined($content)) {  		print(HANDLE $content);  	} @@ -798,9 +846,11 @@ sub lcov_geninfo(@)  	{  		@param = (@param, "--gcov-tool", $gcov_tool);  	} -	if ($ignore_errors) -	{ -		@param = (@param, "--ignore-errors", $ignore_errors); +	foreach (@opt_ignore_errors) { +		@param = (@param, "--ignore-errors", $_); +	} +	if ($no_recursion) { +		@param = (@param, "--no-recursion");  	}  	if ($initial)  	{ @@ -818,6 +868,26 @@ sub lcov_geninfo(@)  	{  		@param = (@param, "--debug");  	} +	if (defined($opt_external) && $opt_external) +	{ +		@param = (@param, "--external"); +	} +	if (defined($opt_external) && !$opt_external) +	{ +		@param = (@param, "--no-external"); +	} +	if (defined($opt_compat)) { +		@param = (@param, "--compat", $opt_compat); +	} +	if (%opt_rc) { +		foreach my $key (keys(%opt_rc)) { +			@param = (@param, "--rc", "$key=".$opt_rc{$key}); +		} +	} +	if (defined($opt_config_file)) { +		@param = (@param, "--config-file", $opt_config_file); +	} +  	system(@param) and exit($? >> 8);  } @@ -834,7 +904,7 @@ sub read_file($)  	local $\;  	local *HANDLE; -	open(HANDLE, "<$filename") || return undef; +	open(HANDLE, "<", $filename) || return undef;  	$content = <HANDLE>;  	close(HANDLE); @@ -860,17 +930,21 @@ sub get_package($)  	local *HANDLE;  	info("Reading package $file:\n"); -	info("  data directory .......: $dir\n");  	$file = abs_path($file);  	chdir($dir); -	open(HANDLE, "tar xvfz $file 2>/dev/null|") +	open(HANDLE, "-|", "tar xvfz '$file' 2>/dev/null")  		or die("ERROR: could not process package $file\n"); +	$count = 0;  	while (<HANDLE>) {  		if (/\.da$/ || /\.gcda$/) {  			$count++;  		}  	}  	close(HANDLE); +	if ($count == 0) { +		die("ERROR: no data file found in package $file\n"); +	} +	info("  data directory .......: $dir\n");  	$build = read_file("$dir/$pkg_build_file");  	if (defined($build)) {  		info("  build directory ......: $build\n"); @@ -904,7 +978,7 @@ sub write_file($$)  	my ($filename, $content) = @_;  	local *HANDLE; -	open(HANDLE, ">$filename") || return 0; +	open(HANDLE, ">", $filename) || return 0;  	print(HANDLE $content);  	close(HANDLE) || return 0; @@ -922,7 +996,7 @@ sub count_package_data($)  	local *HANDLE;  	my $count = 0; -	open(HANDLE, "tar tfz $filename|") or return undef; +	open(HANDLE, "-|", "tar tfz '$filename'") or return undef;  	while (<HANDLE>) {  		if (/\.da$/ || /\.gcda$/) {  			$count++; @@ -944,6 +1018,10 @@ sub create_package($$$;$)  	my ($file, $dir, $build, $gkv) = @_;  	my $cwd = getcwd(); +	# Check for availability of tar tool first +	system("tar --help > /dev/null") +		and die("ERROR: tar command not available\n"); +  	# Print information about the package  	info("Creating package $file:\n");  	info("  data directory .......: $dir\n"); @@ -972,6 +1050,7 @@ sub create_package($$$;$)  	chdir($dir);  	system("tar cfz $file .")  		and die("ERROR: could not create package $file\n"); +	chdir($cwd);  	# Remove temporary files  	unlink("$dir/$pkg_build_file"); @@ -985,7 +1064,6 @@ sub create_package($$$;$)  			info("  data files ...........: $count\n");  		}  	} -	chdir($cwd);  }  sub find_link_fn($$$) @@ -1208,6 +1286,113 @@ sub kernel_capture()  }  # +# link_data_cb(datadir, rel, graphdir) +# +# Create symbolic link in GRAPDIR/REL pointing to DATADIR/REL. +# + +sub link_data_cb($$$) +{ +	my ($datadir, $rel, $graphdir) = @_; +	my $absfrom = catfile($datadir, $rel); +	my $absto = catfile($graphdir, $rel); +	my $base; +	my $dir; + +	if (-e $absto) { +		die("ERROR: could not create symlink at $absto: ". +		    "File already exists!\n"); +	} +	if (-l $absto) { +		# Broken link - possibly from an interrupted earlier run +		unlink($absto); +	} + +	# Check for graph file +	$base = $absto; +	$base =~ s/\.(gcda|da)$//; +	if (! -e $base.".gcno" && ! -e $base.".bbg" && ! -e $base.".bb") { +		die("ERROR: No graph file found for $absfrom in ". +		    dirname($base)."!\n"); +	} + +	symlink($absfrom, $absto) or +		die("ERROR: could not create symlink at $absto: $!\n"); +} + +# +# unlink_data_cb(datadir, rel, graphdir) +# +# Remove symbolic link from GRAPHDIR/REL to DATADIR/REL. +# + +sub unlink_data_cb($$$) +{ +	my ($datadir, $rel, $graphdir) = @_; +	my $absfrom = catfile($datadir, $rel); +	my $absto = catfile($graphdir, $rel); +	my $target; + +	return if (!-l $absto); +	$target = readlink($absto); +	return if (!defined($target) || $target ne $absfrom); + +	unlink($absto) or +		warn("WARNING: could not remove symlink $absto: $!\n"); +} + +# +# link_data(datadir, graphdir, create) +# +# If CREATE is non-zero, create symbolic links in GRAPHDIR for data files +# found in DATADIR. Otherwise remove link in GRAPHDIR. +# + +sub link_data($$$) +{ +	my ($datadir, $graphdir, $create) = @_; + +	$datadir = abs_path($datadir); +	$graphdir = abs_path($graphdir); +	if ($create) { +		lcov_find($datadir, \&link_data_cb, $graphdir, '\.gcda$', +			  '\.da$'); +	} else { +		lcov_find($datadir, \&unlink_data_cb, $graphdir, '\.gcda$', +			  '\.da$'); +	} +} + +# +# find_graph_cb(datadir, rel, count_ref) +# +# Count number of files found. +# + +sub find_graph_cb($$$) +{ +	my ($dir, $rel, $count_ref) = @_; + +	($$count_ref)++; +} + +# +# find_graph(dir) +# +# Search DIR for a graph file. Return non-zero if one was found, zero otherwise. +# + +sub find_graph($) +{ +	my ($dir) = @_; +	my $count = 0; + +	lcov_find($dir, \&find_graph_cb, \$count, '\.gcno$', '\.bb$', '\.bbg$'); + +	return $count > 0 ? 1 : 0; +} + +#  # package_capture()  #  # Capture coverage data from a package of unprocessed coverage data files @@ -1242,7 +1427,16 @@ sub package_capture()  	} else {  		# Build directory needs to be passed to geninfo  		$base_directory = $build; -		lcov_geninfo($dir); +		if (find_graph($dir)) { +			# Package contains graph files - collect from there +			lcov_geninfo($dir); +		} else { +			# No graph files found, link data files next to +			# graph files +			link_data($dir, $base_directory, 1); +			lcov_geninfo($base_directory); +			link_data($dir, $base_directory, 0); +		}  	}  } @@ -1397,10 +1591,12 @@ sub br_ivec_push($$$$)  	my $i;  	$vec = "" if (!defined($vec)); +	$block = $BR_VEC_MAX if $block < 0;  	# Check if branch already exists in vector  	for ($i = 0; $i < $num; $i++) {  		my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); +		$v_block = $BR_VEC_MAX if $v_block < 0;  		next if ($v_block != $block || $v_branch != $branch); @@ -1437,6 +1633,7 @@ sub br_ivec_get($$)  	# Retrieve data from vector  	$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); @@ -1492,6 +1689,10 @@ sub get_br_found_and_hit($)  #        "func"  -> \%funcdata  #        "found" -> $lines_found (number of instrumented lines found in file)  #	 "hit"   -> $lines_hit (number of executed lines in file) +#        "f_found" -> $fn_found (number of instrumented functions found in file) +#	 "f_hit"   -> $fn_hit (number of executed functions in file) +#        "b_found" -> $br_found (number of instrumented branches found in file) +#	 "b_hit"   -> $br_hit (number of executed branches in file)  #        "check" -> \%checkdata  #        "testfnc" -> \%testfncdata  #        "sumfnc"  -> \%sumfnccount @@ -1576,14 +1777,14 @@ sub read_info_file($)  				"compressed file $_[0]!\n");  		# Open compressed file -		open(INFO_HANDLE, "gunzip -c $_[0]|") +		open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'")  			or die("ERROR: cannot start gunzip to decompress ".  			       "file $_[0]!\n");  	}  	else  	{  		# Open decompressed file -		open(INFO_HANDLE, $_[0]) +		open(INFO_HANDLE, "<", $_[0])  			or die("ERROR: cannot read file $_[0]!\n");  	} @@ -1674,6 +1875,8 @@ sub read_info_file($)  			/^FN:(\d+),([^,]+)/ && do  			{ +				last if (!$func_coverage); +  				# Function data found, add to structure  				$funcdata->{$2} = $1; @@ -1692,6 +1895,8 @@ sub read_info_file($)  			/^FNDA:(\d+),([^,]+)/ && do  			{ +				last if (!$func_coverage); +  				# Function call count found, add to structure  				# Add summary counts  				$sumfnccount->{$2} += $1; @@ -1709,6 +1914,7 @@ sub read_info_file($)  				my ($line, $block, $branch, $taken) =  				   ($1, $2, $3, $4); +				last if (!$br_coverage);  				$sumbrcount->{$line} =  					br_ivec_push($sumbrcount->{$line},  						     $block, $branch, $taken); @@ -1912,8 +2118,8 @@ sub set_info_entry($$$$$$$$$;$$$$$$)  sub add_counts($$)  { -	my %data1 = %{$_[0]};	# Hash 1 -	my %data2 = %{$_[1]};	# Hash 2 +	my $data1_ref = $_[0];	# Hash 1 +	my $data2_ref = $_[1];	# Hash 2  	my %result;		# Resulting hash  	my $line;		# Current line iteration scalar  	my $data1_count;	# Count of line in hash1 @@ -1921,10 +2127,10 @@ sub add_counts($$)  	my $found = 0;		# Total number of lines found  	my $hit = 0;		# Number of lines with a count > 0 -	foreach $line (keys(%data1)) +	foreach $line (keys(%$data1_ref))  	{ -		$data1_count = $data1{$line}; -		$data2_count = $data2{$line}; +		$data1_count = $data1_ref->{$line}; +		$data2_count = $data2_ref->{$line};  		# Add counts if present in both hashes  		if (defined($data2_count)) { $data1_count += $data2_count; } @@ -1936,14 +2142,14 @@ sub add_counts($$)  		if ($data1_count > 0) { $hit++; }  	} -	# Add lines unique to data2 -	foreach $line (keys(%data2)) +	# Add lines unique to data2_ref +	foreach $line (keys(%$data2_ref))  	{ -		# Skip lines already in data1 -		if (defined($data1{$line})) { next; } +		# Skip lines already in data1_ref +		if (defined($data1_ref->{$line})) { next; } -		# Copy count from data2 -		$result{$line} = $data2{$line}; +		# Copy count from data2_ref +		$result{$line} = $data2_ref->{$line};  		$found++;  		if ($result{$line} > 0) { $hit++; } @@ -2439,7 +2645,7 @@ sub add_traces()  	if ($to_file)  	{  		info("Writing data to $output_filename\n"); -		open(INFO_HANDLE, ">$output_filename") +		open(INFO_HANDLE, ">", $output_filename)  			or die("ERROR: cannot write to $output_filename!\n");  		@result = write_info_file(*INFO_HANDLE, $total_trace);  		close(*INFO_HANDLE); @@ -2548,6 +2754,7 @@ sub write_info_file(*$)  					my ($block, $branch, $taken) =  						br_ivec_get($brdata, $i); +					$block = $BR_VEC_MAX if ($block < 0);  					print(INFO_HANDLE "BRDA:$line,$block,".  					      "$branch,$taken\n");  					$br_found++; @@ -2589,7 +2796,7 @@ sub write_info_file(*$)  #  # transform_pattern(pattern)  # -# Transform shell wildcard expression to equivalent PERL regular expression. +# Transform shell wildcard expression to equivalent Perl regular expression.  # Return transformed pattern.  # @@ -2669,7 +2876,7 @@ sub extract()  	{  		info("Extracted $extracted files\n");  		info("Writing data to $output_filename\n"); -		open(INFO_HANDLE, ">$output_filename") +		open(INFO_HANDLE, ">", $output_filename)  			or die("ERROR: cannot write to $output_filename!\n");  		@result = write_info_file(*INFO_HANDLE, $data);  		close(*INFO_HANDLE); @@ -2725,7 +2932,7 @@ sub remove()  	{  		info("Deleted $removed files\n");  		info("Writing data to $output_filename\n"); -		open(INFO_HANDLE, ">$output_filename") +		open(INFO_HANDLE, ">", $output_filename)  			or die("ERROR: cannot write to $output_filename!\n");  		@result = write_info_file(*INFO_HANDLE, $data);  		close(*INFO_HANDLE); @@ -2836,13 +3043,13 @@ sub shorten_number($$)  	return '#';  } -sub shorten_rate($$) +sub shorten_rate($$$)  { -	my ($rate, $width) = @_; -	my $result = sprintf("%*.1f%%", $width - 3, $rate); +	my ($hit, $found, $width) = @_; +	my $result = rate($hit, $found, "%", 1, $width);  	return $result if (length($result) <= $width); -	$result = sprintf("%*d%%", $width - 1, $rate); +	$result = rate($hit, $found, "%", 0, $width);  	return $result if (length($result) <= $width);  	return "#";  } @@ -3060,26 +3267,11 @@ sub list()  		$br_total_hit += $br_hit;  		# Determine line coverage rate for this file -		if ($found == 0) { -			$rate = "-"; -		} else { -			$rate = shorten_rate(100 * $hit / $found, -					     $fwidth[$F_LN_RATE]); -		} +		$rate = shorten_rate($hit, $found, $fwidth[$F_LN_RATE]);  		# Determine function coverage rate for this file -		if (!defined($fn_found) || $fn_found == 0) { -			$fnrate = "-"; -		} else { -			$fnrate = shorten_rate(100 * $fn_hit / $fn_found, -					     $fwidth[$F_FN_RATE]); -		} +		$fnrate = shorten_rate($fn_hit, $fn_found, $fwidth[$F_FN_RATE]);  		# Determine branch coverage rate for this file -		if (!defined($br_found) || $br_found == 0) { -			$brrate = "-"; -		} else { -			$brrate = shorten_rate(100 * $br_hit / $br_found, -					     $fwidth[$F_BR_RATE]); -		} +		$brrate = shorten_rate($br_hit, $br_found, $fwidth[$F_BR_RATE]);  		# Assemble line parameters  		push(@file_data, $print_filename); @@ -3095,26 +3287,13 @@ sub list()  	}  	# Determine total line coverage rate -	if ($total_found == 0) { -		$rate = "-"; -	} else { -		$rate = shorten_rate(100 * $total_hit / $total_found, -				     $fwidth[$F_LN_RATE]); -	} +	$rate = shorten_rate($total_hit, $total_found, $fwidth[$F_LN_RATE]);  	# Determine total function coverage rate -	if ($fn_total_found == 0) { -		$fnrate = "-"; -	} else { -		$fnrate = shorten_rate(100 * $fn_total_hit / $fn_total_found, -				       $fwidth[$F_FN_RATE]); -	} +	$fnrate = shorten_rate($fn_total_hit, $fn_total_found, +			       $fwidth[$F_FN_RATE]);  	# Determine total branch coverage rate -	if ($br_total_found == 0) { -		$brrate = "-"; -	} else { -		$brrate = shorten_rate(100 * $br_total_hit / $br_total_found, -				       $fwidth[$F_BR_RATE]); -	} +	$brrate = shorten_rate($br_total_hit, $br_total_found, +			       $fwidth[$F_BR_RATE]);  	# Print separator  	print(("="x$barlen)."\n"); @@ -3253,14 +3432,14 @@ sub read_diff($)  				"compressed file $diff_file!\n");  		# Open compressed file -		open(HANDLE, "gunzip -c $diff_file|") +		open(HANDLE, "-|", "gunzip -c '$diff_file'")  			or die("ERROR: cannot start gunzip to decompress ".  			       "file $_[0]!\n");  	}  	else  	{  		# Open decompressed file -		open(HANDLE, $diff_file) +		open(HANDLE, "<", $diff_file)  			or die("ERROR: cannot read file $_[0]!\n");  	} @@ -3668,10 +3847,10 @@ sub adjust_fncdata($$$)  	# Remove count data in testfncdata for functions which are no longer  	# in funcdata -	foreach $testname (%{$testfncdata}) { +	foreach $testname (keys(%{$testfncdata})) {  		my $fnccount = $testfncdata->{$testname}; -		foreach $func (%{$fnccount}) { +		foreach $func (keys(%{$fnccount})) {  			if (!defined($funcdata->{$func})) {  				delete($fnccount->{$func});  			} @@ -3679,7 +3858,7 @@ sub adjust_fncdata($$$)  	}  	# Remove count data in sumfnccount for functions which are no longer  	# in funcdata -	foreach $func (%{$sumfnccount}) { +	foreach $func (keys(%{$sumfnccount})) {  		if (!defined($funcdata->{$func})) {  			delete($sumfnccount->{$func});  		} @@ -3886,7 +4065,7 @@ sub diff()  	if ($to_file)  	{  		info("Writing data to $output_filename\n"); -		open(INFO_HANDLE, ">$output_filename") +		open(INFO_HANDLE, ">", $output_filename)  			or die("ERROR: cannot write to $output_filename!\n");  		@result = write_info_file(*INFO_HANDLE, $trace_data);  		close(*INFO_HANDLE); @@ -3899,6 +4078,59 @@ sub diff()  	return @result;  } +# +# summary() +# + +sub summary() +{ +	my $filename; +	my $current; +	my $total; +	my $ln_total_found; +	my $ln_total_hit; +	my $fn_total_found; +	my $fn_total_hit; +	my $br_total_found; +	my $br_total_hit; + +	# Read and combine trace files +	foreach $filename (@opt_summary) { +		$current = read_info_file($filename); +		if (!defined($total)) { +			$total = $current; +		} else { +			$total = combine_info_files($total, $current); +		} +	} +	# Calculate coverage data +	foreach $filename (keys(%{$total})) +	{ +		my $entry = $total->{$filename}; +		my $ln_found; +		my $ln_hit; +		my $fn_found; +		my $fn_hit; +		my $br_found; +		my $br_hit; + +		(undef, undef, undef, undef, undef, undef, undef, undef, +			$ln_found, $ln_hit, $fn_found, $fn_hit, $br_found, +			$br_hit) = get_info_entry($entry); + +		# Add to totals +		$ln_total_found	+= $ln_found; +		$ln_total_hit	+= $ln_hit; +		$fn_total_found += $fn_found; +		$fn_total_hit	+= $fn_hit; +		$br_total_found += $br_found; +		$br_total_hit	+= $br_hit; +	} + + +	return ($ln_total_found, $ln_total_hit, $fn_total_found, $fn_total_hit, +		$br_total_found, $br_total_hit); +}  #  # system_no_output(mode, parameters) @@ -3920,12 +4152,12 @@ 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");  	system(@_);  	$result = $?; @@ -3935,8 +4167,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;  } @@ -3957,7 +4189,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; @@ -3996,8 +4228,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($) @@ -4006,8 +4238,9 @@ sub apply_config($)  	foreach (keys(%{$ref}))  	{ -		if (defined($config->{$_})) -		{ +		if (defined($opt_rc{$_})) { +			${$ref->{$_}} = $opt_rc{$_}; +		} elsif (defined($config->{$_})) {  			${$ref->{$_}} = $config->{$_};  		}  	} @@ -4037,6 +4270,9 @@ sub abort_handler($)  sub temp_cleanup()  { +	# Ensure temp directory is not in use by current process +	chdir("/"); +  	if (@temp_dirs) {  		info("Removing temporary directories.\n");  		foreach (@temp_dirs) { @@ -4145,8 +4381,8 @@ sub get_overall_line($$$$)  	return "no data found" if (!defined($found) || $found == 0);  	$name = ($found == 1) ? $name_sn : $name_pl; -	return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit, -		       $found, $name); + +	return rate($hit, $found, "% ($hit of $found $name)");  } @@ -4162,7 +4398,7 @@ sub print_overall_rate($$$$$$$$$)  	my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit,  	    $br_do, $br_found, $br_hit) = @_; -	info("Overall coverage rate:\n"); +	info("Summary coverage rate:\n");  	info("  lines......: %s\n",  	     get_overall_line($ln_found, $ln_hit, "line", "lines"))  		if ($ln_do); @@ -4173,3 +4409,38 @@ sub print_overall_rate($$$$$$$$$)  	     get_overall_line($br_found, $br_hit, "branch", "branches"))  		if ($br_do);  } + + +# +# rate(hit, found[, suffix, precision, width]) +# +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. +# PRECISION specifies the precision of the result. SUFFIX defines a +# string that is appended to the result if FOUND is non-zero. Spaces +# are added to the start of the resulting string until it is at least WIDTH +# characters wide. +# + +sub rate($$;$$$) +{ +        my ($hit, $found, $suffix, $precision, $width) = @_; +        my $rate;  + +	# Assign defaults if necessary +        $precision	= 1	if (!defined($precision)); +	$suffix		= ""	if (!defined($suffix)); +	$width		= 0	if (!defined($width)); +         +        return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); +        $rate = sprintf("%.*f", $precision, $hit * 100 / $found); + +	# Adjust rates if necessary +        if ($rate == 0 && $hit > 0) { +		$rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); +        } elsif ($rate == 100 && $hit != $found) { +		$rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); +	} + +	return sprintf("%*s", $width, $rate.$suffix); +} | 
 Swift
 Swift