| Index: third_party/lcov/bin/geninfo | 
| diff --git a/third_party/lcov/bin/geninfo b/third_party/lcov/bin/geninfo | 
| index 055641baccb7ac65ab8b8eaa62a20e3a3e33957a..9325f9d68c2d1cfb879d534a09951c70d12f9958 100755 | 
| --- a/third_party/lcov/bin/geninfo | 
| +++ b/third_party/lcov/bin/geninfo | 
| @@ -1,6 +1,6 @@ | 
| #!/usr/bin/perl -w | 
| # | 
| -#   Copyright (c) International Business Machines  Corp., 2002,2007 | 
| +#   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 | 
| @@ -51,16 +51,22 @@ | 
|  | 
| use strict; | 
| use File::Basename; | 
| +use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir | 
| +			      splitpath catpath/; | 
| use Getopt::Long; | 
| use Digest::MD5 qw(md5_base64); | 
| - | 
| +if( $^O eq "msys" ) | 
| +{ | 
| +	require File::Spec::Win32; | 
| +} | 
|  | 
| # Constants | 
| -our $lcov_version	= "LCOV version 1.7"; | 
| +our $lcov_version	= 'LCOV version 1.10'; | 
| 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; | 
| @@ -68,41 +74,140 @@ 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"; | 
| + | 
| +# 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; | 
| +our $BR_BRANCH		= 2; | 
| +our $BR_TAKEN		= 3; | 
| +our $BR_VEC_ENTRIES	= 4; | 
| +our $BR_VEC_WIDTH	= 32; | 
| + | 
| +our $UNNAMED_BLOCK	= 9999; | 
|  | 
| # Prototypes | 
| sub print_usage(*); | 
| sub gen_info($); | 
| -sub process_dafile($); | 
| +sub process_dafile($$); | 
| sub match_filename($@); | 
| sub solve_ambiguous_match($$$); | 
| sub split_filename($); | 
| sub solve_relative_path($$); | 
| -sub get_dir($); | 
| sub read_gcov_header($); | 
| sub read_gcov_file($); | 
| -sub read_bb_file($$); | 
| -sub read_string(*$); | 
| -sub read_gcno_file($$); | 
| -sub read_gcno_string(*$); | 
| -sub read_hammer_bbg_file($$); | 
| -sub read_hammer_bbg_string(*$); | 
| -sub unpack_int32($$); | 
| sub info(@); | 
| sub get_gcov_version(); | 
| sub system_no_output($@); | 
| sub read_config($); | 
| sub apply_config($); | 
| -sub gen_initial_info($); | 
| -sub process_graphfile($); | 
| +sub get_exclusion_data($); | 
| +sub apply_exclusion_data($$); | 
| +sub process_graphfile($$); | 
| +sub filter_fn_name($); | 
| sub warn_handler($); | 
| sub die_handler($); | 
| +sub graph_error($$); | 
| +sub graph_expect($); | 
| +sub graph_read(*$;$$); | 
| +sub graph_skip(*$;$); | 
| +sub sort_uniq(@); | 
| +sub sort_uniq_lex(@); | 
| +sub graph_cleanup($); | 
| +sub graph_find_base($); | 
| +sub graph_from_bb($$$); | 
| +sub graph_add_order($$$); | 
| +sub read_bb_word(*;$); | 
| +sub read_bb_value(*;$); | 
| +sub read_bb_string(*$); | 
| +sub read_bb($); | 
| +sub read_bbg_word(*;$); | 
| +sub read_bbg_value(*;$); | 
| +sub read_bbg_string(*); | 
| +sub read_bbg_lines_record(*$$$$$); | 
| +sub read_bbg($); | 
| +sub read_gcno_word(*;$$); | 
| +sub read_gcno_value(*$;$$); | 
| +sub read_gcno_string(*$); | 
| +sub read_gcno_lines_record(*$$$$$$); | 
| +sub determine_gcno_split_crc($$$); | 
| +sub read_gcno_function_record(*$$$$); | 
| +sub read_gcno($); | 
| +sub get_gcov_capabilities(); | 
| +sub get_overall_line($$$$); | 
| +sub print_overall_rate($$$$$$$$$); | 
| +sub br_gvec_len($); | 
| +sub br_gvec_get($$); | 
| +sub debug($); | 
| +sub int_handler(); | 
| +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; | 
| @@ -115,18 +220,35 @@ our $version; | 
| our $follow; | 
| our $checksum; | 
| our $no_checksum; | 
| -our $preserve_paths; | 
| -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; | 
| 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 $cwd = `pwd`; | 
| chomp($cwd); | 
| @@ -141,8 +263,22 @@ $SIG{"INT"} = \&int_handler; | 
| $SIG{__WARN__} = \&warn_handler; | 
| $SIG{__DIE__} = \&die_handler; | 
|  | 
| +# Prettify version string | 
| +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; | 
| + | 
| +# Set LANG so that gcov output will be in a unified format | 
| +$ENV{"LANG"} = "C"; | 
| + | 
| +# 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"); | 
| + | 
| # Read configuration file if available | 
| -if (-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"); | 
| } | 
| @@ -151,15 +287,23 @@ 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, | 
| +	}); | 
|  | 
| # Merge options | 
| if (defined($no_checksum)) | 
| @@ -167,24 +311,53 @@ 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; | 
| +		} | 
| +	} | 
| } | 
|  | 
| # Parse command line options | 
| -if (!GetOptions("test-name=s" => \$test_name, | 
| -		"output-filename=s" => \$output_filename, | 
| +if (!GetOptions("test-name|t=s" => \$test_name, | 
| +		"output-filename|o=s" => \$output_filename, | 
| "checksum" => \$checksum, | 
| "no-checksum" => \$no_checksum, | 
| -		"base-directory=s" => \$base_directory, | 
| -		"version" =>\$version, | 
| -		"quiet" => \$quiet, | 
| -		"help|?" => \$help, | 
| -		"follow" => \$follow, | 
| -		"compat-libtool" => \$compat_libtool, | 
| -		"no-compat-libtool" => \$no_compat_libtool, | 
| +		"base-directory|b=s" => \$base_directory, | 
| +		"version|v" =>\$version, | 
| +		"quiet|q" => \$quiet, | 
| +		"help|h|?" => \$help, | 
| +		"follow|f" => \$follow, | 
| +		"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, | 
| "no-recursion" => \$no_recursion, | 
| +		"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"); | 
| @@ -199,10 +372,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; | 
| } | 
| } | 
|  | 
| @@ -222,6 +400,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) | 
| { | 
| @@ -263,17 +465,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) | 
| { | 
| @@ -302,41 +493,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; }; | 
| -		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"; | 
| @@ -353,19 +512,13 @@ else | 
| $graph_file_extension = ".gcno"; | 
| } | 
|  | 
| -# Check for availability of --preserve-paths option of gcov | 
| -if (`$gcov_tool --help` =~ /--preserve-paths/) | 
| -{ | 
| -	$preserve_paths = "--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); | 
|  | 
| @@ -377,20 +530,20 @@ 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 | 
| -if ($initial) | 
| -{ | 
| -	foreach (@data_directory) | 
| -	{ | 
| -		gen_initial_info($_); | 
| -	} | 
| +foreach my $entry (@data_directory) { | 
| +	gen_info($entry); | 
| } | 
| -else | 
| -{ | 
| -	foreach (@data_directory) | 
| -	{ | 
| -		gen_info($_); | 
| -	} | 
| + | 
| +if ($initial && $br_coverage) { | 
| +	warn("Note: --initial does not generate branch coverage ". | 
| +	     "data\n"); | 
| } | 
| info("Finished .info-file creation\n"); | 
|  | 
| @@ -426,15 +579,54 @@ sequentially. | 
| --(no-)checksum               Enable (disable) line checksumming | 
| --(no-)compat-libtool         Enable (disable) libtool compatibility mode | 
| --gcov-tool TOOL              Specify gcov tool location | 
| -      --ignore-errors ERROR         Continue after ERROR (gcov, source) | 
| -      --no-recursion                Exlude subdirectories from processing | 
| -      --function-coverage           Capture function call counts | 
| +      --ignore-errors ERROR         Continue after ERROR (gcov, source, graph) | 
| +      --no-recursion                Exclude subdirectories from processing | 
| +      --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 | 
| ; | 
| } | 
|  | 
| +# | 
| +# get_common_prefix(min_dir, filenames) | 
| +# | 
| +# Return the longest path prefix shared by all filenames. MIN_DIR specifies | 
| +# the minimum number of directories that a filename may have after removing | 
| +# the prefix. | 
| +# | 
| + | 
| +sub get_common_prefix($@) | 
| +{ | 
| +	my ($min_dir, @files) = @_; | 
| +	my $file; | 
| +	my @prefix; | 
| +	my $i; | 
| + | 
| +	foreach $file (@files) { | 
| +		my ($v, $d, $f) = splitpath($file); | 
| +		my @comp = splitdir($d); | 
| + | 
| +		if (!@prefix) { | 
| +			@prefix = @comp; | 
| +			next; | 
| +		} | 
| +		for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) { | 
| +			if ($comp[$i] ne $prefix[$i] || | 
| +			    ((scalar(@comp) - ($i + 1)) <= $min_dir)) { | 
| +				delete(@prefix[$i..scalar(@prefix)]); | 
| +				last; | 
| +			} | 
| +		} | 
| +	} | 
| + | 
| +	return catdir(@prefix); | 
| +} | 
|  | 
| # | 
| # gen_info(directory) | 
| @@ -473,52 +665,190 @@ sub gen_info($) | 
| { | 
| my $directory = $_[0]; | 
| my @file_list; | 
| +	my $file; | 
| +	my $prefix; | 
| +	my $type; | 
| +	my $ext; | 
| + | 
| +	if ($initial) { | 
| +		$type = "graph"; | 
| +		$ext = $graph_file_extension; | 
| +	} else { | 
| +		$type = "data"; | 
| +		$ext = $data_file_extension; | 
| +	} | 
|  | 
| if (-d $directory) | 
| { | 
| -		info("Scanning $directory for $data_file_extension ". | 
| -		     "files ...\n"); | 
| +		info("Scanning $directory for $ext files ...\n"); | 
|  | 
| -		@file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -type f 2>/dev/null`; | 
| +		@file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`; | 
| chomp(@file_list); | 
| -		@file_list or die("ERROR: no $data_file_extension files found ". | 
| -				  "in $directory!\n"); | 
| -		info("Found %d data files in %s\n", $#file_list+1, $directory); | 
| +		@file_list or | 
| +			die("ERROR: no $ext files found in $directory!\n"); | 
| +		$prefix = get_common_prefix(1, @file_list); | 
| +		info("Found %d %s files in %s\n", $#file_list+1, $type, | 
| +		     $directory); | 
| } | 
| else | 
| { | 
| @file_list = ($directory); | 
| +		$prefix = ""; | 
| } | 
|  | 
| # Process all files in list | 
| -	foreach (@file_list) { process_dafile($_); } | 
| +	foreach $file (@file_list) { | 
| +		# Process file | 
| +		if ($initial) { | 
| +			process_graphfile($file, $prefix); | 
| +		} else { | 
| +			process_dafile($file, $prefix); | 
| +		} | 
| +	} | 
| } | 
|  | 
|  | 
| # | 
| -# process_dafile(da_filename) | 
| +# derive_data(contentdata, funcdata, bbdata) | 
| # | 
| -# Create a .info file for a single data file. | 
| +# 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) = @_; | 
| +	my @gcov_content = @{$contentdata}; | 
| +	my @gcov_functions = @{$funcdata}; | 
| +	my %fn_count; | 
| +	my %ln_fn; | 
| +	my $line; | 
| +	my $maxline; | 
| +	my %fn_name; | 
| +	my $fn; | 
| +	my $count; | 
| + | 
| +	if (!defined($bbdata)) { | 
| +		return @gcov_functions; | 
| +	} | 
| + | 
| +	# First add existing function data | 
| +	while (@gcov_functions) { | 
| +		$count = shift(@gcov_functions); | 
| +		$fn = shift(@gcov_functions); | 
| + | 
| +		$fn_count{$fn} = $count; | 
| +	} | 
| + | 
| +	# Convert line coverage data to function data | 
| +	foreach $fn (keys(%{$bbdata})) { | 
| +		my $line_data = $bbdata->{$fn}; | 
| +		my $line; | 
| +		my $fninstr = 0; | 
| + | 
| +		if ($fn eq "") { | 
| +			next; | 
| +		} | 
| +		# 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; | 
| +	} | 
| + | 
| + | 
| +	# Check if we got data for all functions | 
| +	foreach $fn (keys(%fn_name)) { | 
| +		if ($fn eq "") { | 
| +			next; | 
| +		} | 
| +		if (defined($fn_count{$fn})) { | 
| +			next; | 
| +		} | 
| +		warn("WARNING: no derived data found for function $fn\n"); | 
| +	} | 
| + | 
| +	# Convert hash to list in @gcov_functions format | 
| +	foreach $fn (sort(keys(%fn_count))) { | 
| +		push(@gcov_functions, $fn_count{$fn}, $fn); | 
| +	} | 
| + | 
| +	return @gcov_functions; | 
| +} | 
| + | 
| +# | 
| +# get_filenames(directory, pattern) | 
| +# | 
| +# Return a list of filenames found in directory which match the specified | 
| +# pattern. | 
| # | 
| # Die on error. | 
| # | 
|  | 
| -sub process_dafile($) | 
| +sub get_filenames($$) | 
| { | 
| -	info("Processing %s\n", $_[0]); | 
| +	my ($dirname, $pattern) = @_; | 
| +	my @result; | 
| +	my $directory; | 
| +	local *DIR; | 
| + | 
| +	opendir(DIR, $dirname) or | 
| +		die("ERROR: cannot read directory $dirname\n"); | 
| +	while ($directory = readdir(DIR)) { | 
| +		push(@result, $directory) if ($directory =~ /$pattern/); | 
| +	} | 
| +	closedir(DIR); | 
| + | 
| +	return @result; | 
| +} | 
| + | 
| +# | 
| +# process_dafile(da_filename, dir) | 
| +# | 
| +# Create a .info file for a single data file. | 
| +# | 
| +# Die on error. | 
| +# | 
|  | 
| +sub process_dafile($$) | 
| +{ | 
| +	my ($file, $dir) = @_; | 
| my $da_filename;	# Name of data file to process | 
| my $da_dir;		# Directory of data file | 
| my $source_dir;		# Directory of source file | 
| my $da_basename;	# data filename without ".da/.gcda" extension | 
| my $bb_filename;	# Name of respective graph file | 
| -	my %bb_content;		# Contents of graph file | 
| +	my $bb_basename;	# Basename of the original graph file | 
| +	my $graph;		# Contents of graph file | 
| +	my $instr;		# Contents of graph file part 2 | 
| my $gcov_error;		# Error code of gcov tool | 
| my $object_dir;		# Directory containing all object files | 
| my $source_filename;	# Name of a source code file | 
| my $gcov_file;		# Name of a .gcov file | 
| my @gcov_content;	# Content of a .gcov file | 
| -	my @gcov_branches;	# Branch content of a .gcov file | 
| +	my $gcov_branches;	# Branch content of a .gcov file | 
| my @gcov_functions;	# Function calls of a .gcov file | 
| my @gcov_list;		# List of generated .gcov files | 
| my $line_number;	# Line number count | 
| @@ -526,28 +856,31 @@ sub process_dafile($) | 
| my $lines_found;	# Number of instrumented lines found | 
| my $funcs_hit;		# Number of instrumented functions hit | 
| my $funcs_found;	# Number of instrumented functions found | 
| +	my $br_hit; | 
| +	my $br_found; | 
| my $source;		# gcov source header information | 
| my $object;		# gcov object header information | 
| my @matches;		# List of absolute paths matching filename | 
| my @unprocessed;	# List of unprocessed source code files | 
| my $base_dir;		# Base directory for current file | 
| +	my @tmp_links;		# Temporary links to be cleaned up | 
| my @result; | 
| my $index; | 
| my $da_renamed;		# If data file is to be renamed | 
| local *INFO_HANDLE; | 
|  | 
| +	info("Processing %s\n", abs2rel($file, $dir)); | 
| # Get path to data file in absolute and normalized form (begins with /, | 
| # contains no more ../ or ./) | 
| -	$da_filename = solve_relative_path($cwd, $_[0]); | 
| +	$da_filename = solve_relative_path($cwd, $file); | 
|  | 
| # Get directory and basename of data file | 
| ($da_dir, $da_basename) = split_filename($da_filename); | 
|  | 
| -	# 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) | 
| @@ -577,7 +910,8 @@ sub process_dafile($) | 
| } | 
|  | 
| # Construct name of graph file | 
| -	$bb_filename = $da_dir."/".$da_basename.$graph_file_extension; | 
| +	$bb_basename = $da_basename.$graph_file_extension; | 
| +	$bb_filename = "$da_dir/$bb_basename"; | 
|  | 
| # Find out the real location of graph file in case we're just looking at | 
| # a link | 
| @@ -601,21 +935,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)) | 
| { | 
| -			%bb_content = read_hammer_bbg_file($bb_filename, | 
| -							   $base_dir); | 
| +			($instr, $graph) = read_bbg($bb_filename); | 
| } | 
| else | 
| { | 
| -			%bb_content = read_bb_file($bb_filename, $base_dir); | 
| +			($instr, $graph) = read_bb($bb_filename); | 
| } | 
| } | 
| else | 
| { | 
| -		%bb_content = read_gcno_file($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. | 
| @@ -630,9 +970,21 @@ sub process_dafile($) | 
| "$object_dir/$da_basename$data_file_extension") | 
| and die ("ERROR: cannot create link $object_dir/". | 
| "$da_basename$data_file_extension!\n"); | 
| +		push(@tmp_links, | 
| +		     "$object_dir/$da_basename$data_file_extension"); | 
| +		# Need to create link to graph file if basename of link | 
| +		# and file are different (CONFIG_MODVERSION compat) | 
| +		if ((basename($bb_filename) ne $bb_basename) && | 
| +		    (! -e "$object_dir/$bb_basename")) { | 
| +			symlink($bb_filename, "$object_dir/$bb_basename") or | 
| +				warn("WARNING: cannot create link ". | 
| +				     "$object_dir/$bb_basename\n"); | 
| +			push(@tmp_links, "$object_dir/$bb_basename"); | 
| +		} | 
| } | 
|  | 
| # Change to directory containing data files and apply GCOV | 
| +	debug("chdir($base_dir)\n"); | 
| chdir($base_dir); | 
|  | 
| if ($da_renamed) | 
| @@ -644,19 +996,8 @@ sub process_dafile($) | 
| } | 
|  | 
| # Execute gcov command and suppress standard output | 
| -	if ($preserve_paths) | 
| -	{ | 
| -		$gcov_error = system_no_output(1, $gcov_tool, $da_filename, | 
| -					       "-o", $object_dir, | 
| -					       "--preserve-paths", | 
| -					       "-b"); | 
| -	} | 
| -	else | 
| -	{ | 
| -		$gcov_error = system_no_output(1, $gcov_tool, $da_filename, | 
| -					       "-o", $object_dir, | 
| -					       "-b"); | 
| -	} | 
| +	$gcov_error = system_no_output(1, $gcov_tool, $da_filename, | 
| +				       "-o", $object_dir, @gcov_options); | 
|  | 
| if ($da_renamed) | 
| { | 
| @@ -664,10 +1005,9 @@ sub process_dafile($) | 
| and die ("ERROR: cannot rename $da_filename.ori"); | 
| } | 
|  | 
| -	# Clean up link | 
| -	if ($object_dir ne $da_dir) | 
| -	{ | 
| -		unlink($object_dir."/".$da_basename.$data_file_extension); | 
| +	# Clean up temporary links | 
| +	foreach (@tmp_links) { | 
| +		unlink($_); | 
| } | 
|  | 
| if ($gcov_error) | 
| @@ -681,7 +1021,7 @@ sub process_dafile($) | 
| } | 
|  | 
| # Collect data from resulting .gcov files and create .info file | 
| -	@gcov_list = glob("*.gcov"); | 
| +	@gcov_list = get_filenames('.', '\.gcov$'); | 
|  | 
| # Check for files | 
| if (!@gcov_list) | 
| @@ -700,7 +1040,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"); | 
| } | 
| @@ -708,7 +1048,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"); | 
| } | 
|  | 
| @@ -717,20 +1057,35 @@ sub process_dafile($) | 
|  | 
| # Traverse the list of generated .gcov files and combine them into a | 
| # single .info file | 
| -	@unprocessed = keys(%bb_content); | 
| -	foreach $gcov_file (@gcov_list) | 
| +	@unprocessed = keys(%{$instr}); | 
| +	foreach $gcov_file (sort(@gcov_list)) | 
| { | 
| +		my $i; | 
| +		my $num; | 
| + | 
| +		# 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]) | 
| { | 
| @@ -741,8 +1096,7 @@ sub process_dafile($) | 
| die("ERROR: could not read source file $source\n"); | 
| } | 
|  | 
| -		@matches = match_filename(defined($source) ? $source : | 
| -					  $gcov_file, keys(%bb_content)); | 
| +		@matches = match_filename($source, keys(%{$instr})); | 
|  | 
| # Skip files that are not mentioned in the graph file | 
| if (!@matches) | 
| @@ -756,8 +1110,14 @@ sub process_dafile($) | 
|  | 
| # Read in contents of gcov file | 
| @result = read_gcov_file($gcov_file); | 
| +		if (!defined($result[0])) { | 
| +			warn("WARNING: skipping unreadable file ". | 
| +			     $gcov_file."\n"); | 
| +			unlink($gcov_file); | 
| +			next; | 
| +		} | 
| @gcov_content = @{$result[0]}; | 
| -		@gcov_branches = @{$result[1]}; | 
| +		$gcov_branches = $result[1]; | 
| @gcov_functions = @{$result[2]}; | 
|  | 
| # Skip empty files | 
| @@ -790,22 +1150,61 @@ sub process_dafile($) | 
| } | 
| } | 
|  | 
| +		# 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; | 
| +			} | 
| +		} | 
| + | 
| # Write absolute path of source file | 
| printf(INFO_HANDLE "SF:%s\n", $source_filename); | 
|  | 
| +		# If requested, derive function coverage data from | 
| +		# line coverage data of the first line of a function | 
| +		if ($opt_derive_func_data) { | 
| +			@gcov_functions = | 
| +				derive_data(\@gcov_content, \@gcov_functions, | 
| +					    $graph->{$source_filename}); | 
| +		} | 
| + | 
| # Write function-related information | 
| -		if (defined($bb_content{$source_filename})) | 
| +		if (defined($graph->{$source_filename})) | 
| { | 
| -			foreach (split(",",$bb_content{$source_filename})) | 
| -			{ | 
| -				my ($fn, $line) = split("=", $_); | 
| +			my $fn_data = $graph->{$source_filename}; | 
| +			my $fn; | 
|  | 
| +			foreach $fn (sort | 
| +				{$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]} | 
| +				keys(%{$fn_data})) { | 
| +				my $ln_data = $fn_data->{$fn}; | 
| +				my $line = $ln_data->[0]; | 
| + | 
| +				# Skip empty function | 
| if ($fn eq "") { | 
| next; | 
| } | 
| +				# Remove excluded functions | 
| +				if (!$no_markers) { | 
| +					my $gfn; | 
| +					my $found = 0; | 
| + | 
| +					foreach $gfn (@gcov_functions) { | 
| +						if ($gfn eq $fn) { | 
| +							$found = 1; | 
| +							last; | 
| +						} | 
| +					} | 
| +					if (!$found) { | 
| +						next; | 
| +					} | 
| +				} | 
|  | 
| # Normalize function name | 
| -				$fn =~ s/\W/_/g; | 
| +				$fn = filter_fn_name($fn); | 
|  | 
| print(INFO_HANDLE "FN:$line,$fn\n"); | 
| } | 
| @@ -820,18 +1219,42 @@ sub process_dafile($) | 
| $funcs_hit = 0; | 
| while (@gcov_functions) | 
| { | 
| -			printf(INFO_HANDLE "FNDA:%s,%s\n", | 
| -				       $gcov_functions[0], | 
| -				       $gcov_functions[1]); | 
| -				$funcs_found++; | 
| -			$funcs_hit++ if $gcov_functions[0]; | 
| -			splice(@gcov_functions,0,2); | 
| +			my $count = shift(@gcov_functions); | 
| +			my $fn = shift(@gcov_functions); | 
| + | 
| +			$fn = filter_fn_name($fn); | 
| +			printf(INFO_HANDLE "FNDA:$count,$fn\n"); | 
| +			$funcs_found++; | 
| +			$funcs_hit++ if ($count > 0); | 
| } | 
| if ($funcs_found > 0) { | 
| printf(INFO_HANDLE "FNF:%s\n", $funcs_found); | 
| printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); | 
| } | 
|  | 
| +		# Write coverage information for each instrumented branch: | 
| +		# | 
| +		#   BRDA:<line number>,<block number>,<branch number>,<taken> | 
| +		# | 
| +		# where 'taken' is the number of times the branch was taken | 
| +		# or '-' if the block to which the branch belongs was never | 
| +		# executed | 
| +		$br_found = 0; | 
| +		$br_hit = 0; | 
| +		$num = br_gvec_len($gcov_branches); | 
| +		for ($i = 0; $i < $num; $i++) { | 
| +			my ($line, $block, $branch, $taken) = | 
| +				br_gvec_get($gcov_branches, $i); | 
| + | 
| +			print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); | 
| +			$br_found++; | 
| +			$br_hit++ if ($taken ne '-' && $taken > 0); | 
| +		} | 
| +		if ($br_found > 0) { | 
| +			printf(INFO_HANDLE "BRF:%s\n", $br_found); | 
| +			printf(INFO_HANDLE "BRH:%s\n", $br_hit); | 
| +		} | 
| + | 
| # Reset line counters | 
| $line_number = 0; | 
| $lines_found = 0; | 
| @@ -862,27 +1285,6 @@ sub process_dafile($) | 
| splice(@gcov_content,0,3); | 
| } | 
|  | 
| -		#-- | 
| -		#-- BA: <code-line>, <branch-coverage> | 
| -		#-- | 
| -		#-- print one BA line for every branch of a | 
| -		#-- conditional.  <branch-coverage> values | 
| -		#-- are: | 
| -		#--     0 - not executed | 
| -		#--     1 - executed but not taken | 
| -		#--     2 - executed and taken | 
| -		#-- | 
| -		while (@gcov_branches) | 
| -		{ | 
| -			if ($gcov_branches[0]) | 
| -			{ | 
| -				printf(INFO_HANDLE "BA:%s,%s\n", | 
| -				       $gcov_branches[0], | 
| -				       $gcov_branches[1]); | 
| -			} | 
| -			splice(@gcov_branches,0,2); | 
| -		} | 
| - | 
| # Write line statistics and section separator | 
| printf(INFO_HANDLE "LF:%s\n", $lines_found); | 
| printf(INFO_HANDLE "LH:%s\n", $lines_hit); | 
| @@ -922,8 +1324,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 =~ /^[^\/]/) | 
| @@ -936,6 +1370,10 @@ sub solve_relative_path($$) | 
|  | 
| # Remove . | 
| $result =~ s/\/\.\//\//g; | 
| +	$result =~ s/\/\.$/\//g; | 
| + | 
| +	# Remove trailing / | 
| +	$result =~ s/\/$//g; | 
|  | 
| # Solve .. | 
| while ($result =~ s/\/[^\/]+\/\.\.\//\//) | 
| @@ -958,28 +1396,42 @@ sub solve_relative_path($$) | 
|  | 
| sub match_filename($@) | 
| { | 
| -	my $filename = shift; | 
| -	my @list = @_; | 
| +	my ($filename, @list) = @_; | 
| +	my ($vol, $dir, $file) = splitpath($filename); | 
| +	my @comp = splitdir($dir); | 
| +	my $comps = scalar(@comp); | 
| +	my $entry; | 
| my @result; | 
|  | 
| -	$filename =~ s/^(.*).gcov$/$1/; | 
| - | 
| -	if ($filename =~ /^\/(.*)$/) | 
| -	{ | 
| -		$filename = "$1"; | 
| -	} | 
| +entry: | 
| +	foreach $entry (@list) { | 
| +		my ($evol, $edir, $efile) = splitpath($entry); | 
| +		my @ecomp; | 
| +		my $ecomps; | 
| +		my $i; | 
|  | 
| -	foreach (@list) | 
| -	{ | 
| -		if (/\/\Q$filename\E(.*)$/ && $1 eq "") | 
| -		{ | 
| -			@result = (@result, $_); | 
| +		# Filename component must match | 
| +		if ($efile ne $file) { | 
| +			next; | 
| +		} | 
| +		# Check directory components last to first for match | 
| +		@ecomp = splitdir($edir); | 
| +		$ecomps = scalar(@ecomp); | 
| +		if ($ecomps < $comps) { | 
| +			next; | 
| +		} | 
| +		for ($i = 0; $i < $comps; $i++) { | 
| +			if ($comp[$comps - $i - 1] ne | 
| +			    $ecomp[$ecomps - $i - 1]) { | 
| +				next entry; | 
| +			} | 
| } | 
| +		push(@result, $entry), | 
| } | 
| + | 
| return @result; | 
| } | 
|  | 
| - | 
| # | 
| # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) | 
| # | 
| @@ -1006,7 +1458,7 @@ sub solve_ambiguous_match($$$) | 
| { | 
|  | 
| # Compare file contents | 
| -		open(SOURCE, $filename) | 
| +		open(SOURCE, "<", $filename) | 
| or die("ERROR: cannot read $filename!\n"); | 
|  | 
| $no_match = 0; | 
| @@ -1014,6 +1466,9 @@ sub solve_ambiguous_match($$$) | 
| { | 
| chomp; | 
|  | 
| +			# Also remove CR from line-end | 
| +			s/\015$//; | 
| + | 
| if ($_ ne @$content[$index]) | 
| { | 
| $no_match = 1; | 
| @@ -1052,21 +1507,6 @@ sub split_filename($) | 
|  | 
|  | 
| # | 
| -# get_dir(filename); | 
| -# | 
| -# Return the directory component of a given FILENAME. | 
| -# | 
| - | 
| -sub get_dir($) | 
| -{ | 
| -	my @components = split("/", $_[0]); | 
| -	pop(@components); | 
| - | 
| -	return join("/", @components); | 
| -} | 
| - | 
| - | 
| -# | 
| # read_gcov_header(gcov_filename) | 
| # | 
| # Parse file GCOV_FILENAME and return a list containing the following | 
| @@ -1088,7 +1528,7 @@ sub read_gcov_header($) | 
| my $object; | 
| local *INPUT; | 
|  | 
| -	if (!open(INPUT, $_[0])) | 
| +	if (!open(INPUT, "<", $_[0])) | 
| { | 
| if ($ignore_errors[$ERROR_GCOV]) | 
| { | 
| @@ -1102,6 +1542,9 @@ sub read_gcov_header($) | 
| { | 
| chomp($_); | 
|  | 
| +		# Also remove CR from line-end | 
| +		s/\015$//; | 
| + | 
| if (/^\s+-:\s+0:Source:(.*)$/) | 
| { | 
| # Source: header entry | 
| @@ -1125,6 +1568,84 @@ sub read_gcov_header($) | 
|  | 
|  | 
| # | 
| +# br_gvec_len(vector) | 
| +# | 
| +# Return the number of entries in the branch coverage vector. | 
| +# | 
| + | 
| +sub br_gvec_len($) | 
| +{ | 
| +	my ($vec) = @_; | 
| + | 
| +	return 0 if (!defined($vec)); | 
| +	return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_gvec_get(vector, number) | 
| +# | 
| +# Return an entry from the branch coverage vector. | 
| +# | 
| + | 
| +sub br_gvec_get($$) | 
| +{ | 
| +	my ($vec, $num) = @_; | 
| +	my $line; | 
| +	my $block; | 
| +	my $branch; | 
| +	my $taken; | 
| +	my $offset = $num * $BR_VEC_ENTRIES; | 
| + | 
| +	# Retrieve data from vector | 
| +	$line	= vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); | 
| +	$block	= vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); | 
| +	$branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); | 
| +	$taken	= vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); | 
| + | 
| +	# Decode taken value from an integer | 
| +	if ($taken == 0) { | 
| +		$taken = "-"; | 
| +	} else { | 
| +		$taken--; | 
| +	} | 
| + | 
| +	return ($line, $block, $branch, $taken); | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_gvec_push(vector, line, block, branch, taken) | 
| +# | 
| +# Add an entry to the branch coverage vector. | 
| +# | 
| + | 
| +sub br_gvec_push($$$$$) | 
| +{ | 
| +	my ($vec, $line, $block, $branch, $taken) = @_; | 
| +	my $offset; | 
| + | 
| +	$vec = "" if (!defined($vec)); | 
| +	$offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; | 
| + | 
| +	# Encode taken value into an integer | 
| +	if ($taken eq "-") { | 
| +		$taken = 0; | 
| +	} else { | 
| +		$taken++; | 
| +	} | 
| + | 
| +	# Add to vector | 
| +	vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line; | 
| +	vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; | 
| +	vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; | 
| +	vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; | 
| + | 
| +	return $vec; | 
| +} | 
| + | 
| + | 
| +# | 
| # read_gcov_file(gcov_filename) | 
| # | 
| # Parse file GCOV_FILENAME (.gcov file format) and return the list: | 
| @@ -1137,8 +1658,8 @@ sub read_gcov_header($) | 
| # $result[($line_number-1)*3+1] = execution count for line $line_number | 
| # $result[($line_number-1)*3+2] = source code text for line $line_number | 
| # | 
| -# gcov_branch is a list of 2 elements | 
| -# (linenumber, branch result) for each branch | 
| +# gcov_branch is a vector of 4 4-byte long elements for each branch: | 
| +# line number, block number, branch number, count + 1 or 0 | 
| # | 
| # gcov_func is a list of 2 elements | 
| # (number of calls, function name) for each function | 
| @@ -1150,13 +1671,23 @@ sub read_gcov_file($) | 
| { | 
| my $filename = $_[0]; | 
| my @result = (); | 
| -	my @branches = (); | 
| +	my $branches = ""; | 
| my @functions = (); | 
| my $number; | 
| +	my $exclude_flag = 0; | 
| +	my $exclude_line = 0; | 
| +	my $last_block = $UNNAMED_BLOCK; | 
| +	my $last_line = 0; | 
| local *INPUT; | 
|  | 
| -	open(INPUT, $filename) | 
| -		or die("ERROR: cannot read $filename!\n"); | 
| +	if (!open(INPUT, "<", $filename)) { | 
| +		if ($ignore_errors[$ERROR_GCOV]) | 
| +		{ | 
| +			warn("WARNING: cannot read $filename!\n"); | 
| +			return (undef, undef, undef); | 
| +		} | 
| +		die("ERROR: cannot read $filename!\n"); | 
| +	} | 
|  | 
| if ($gcov_version < $GCOV_VERSION_3_3_0) | 
| { | 
| @@ -1165,29 +1696,19 @@ sub read_gcov_file($) | 
| { | 
| chomp($_); | 
|  | 
| -			if (/^\t\t(.*)$/) | 
| -			{ | 
| -				# Uninstrumented line | 
| -				push(@result, 0); | 
| -				push(@result, 0); | 
| -				push(@result, $1); | 
| -			} | 
| -			elsif (/^branch/) | 
| -			{ | 
| -				# Branch execution data | 
| -				push(@branches, scalar(@result) / 3); | 
| -				if (/^branch \d+ never executed$/) | 
| -				{ | 
| -					push(@branches, 0); | 
| -				} | 
| -				elsif (/^branch \d+ taken = 0%/) | 
| -				{ | 
| -					push(@branches, 1); | 
| -				} | 
| -				else | 
| -				{ | 
| -					push(@branches, 2); | 
| -				} | 
| +			# Also remove CR from line-end | 
| +			s/\015$//; | 
| + | 
| +			if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { | 
| +				next if (!$br_coverage); | 
| +				next if ($exclude_line); | 
| +				$branches = br_gvec_push($branches, $last_line, | 
| +						$last_block, $1, $2); | 
| +			} elsif (/^branch\s+(\d+)\s+never\s+executed/) { | 
| +				next if (!$br_coverage); | 
| +				next if ($exclude_line); | 
| +				$branches = br_gvec_push($branches, $last_line, | 
| +						$last_block, $1, '-'); | 
| } | 
| elsif (/^call/ || /^function/) | 
| { | 
| @@ -1195,15 +1716,43 @@ sub read_gcov_file($) | 
| } | 
| else | 
| { | 
| +				$last_line++; | 
| +				# Check for exclusion markers | 
| +				if (!$no_markers) { | 
| +					if (/$EXCL_STOP/) { | 
| +						$exclude_flag = 0; | 
| +					} elsif (/$EXCL_START/) { | 
| +						$exclude_flag = 1; | 
| +					} | 
| +					if (/$EXCL_LINE/ || $exclude_flag) { | 
| +						$exclude_line = 1; | 
| +					} else { | 
| +						$exclude_line = 0; | 
| +					} | 
| +				} | 
| # Source code execution data | 
| -				$number = (split(" ",substr($_, 0, 16)))[0]; | 
| +				if (/^\t\t(.*)$/) | 
| +				{ | 
| +					# Uninstrumented line | 
| +					push(@result, 0); | 
| +					push(@result, 0); | 
| +					push(@result, $1); | 
| +					next; | 
| +				} | 
| +				$number = (split(" ",substr($_, 0, 16)))[0]; | 
|  | 
| # Check for zero count which is indicated | 
| # by ###### | 
| if ($number eq "######") { $number = 0;	} | 
|  | 
| -				push(@result, 1); | 
| -				push(@result, $number); | 
| +				if ($exclude_line) { | 
| +					# Register uninstrumented line instead | 
| +					push(@result, 0); | 
| +					push(@result, 0); | 
| +				} else { | 
| +					push(@result, 1); | 
| +					push(@result, $number); | 
| +				} | 
| push(@result, substr($_, 16)); | 
| } | 
| } | 
| @@ -1215,25 +1764,31 @@ sub read_gcov_file($) | 
| { | 
| chomp($_); | 
|  | 
| -			if (/^branch\s+\d+\s+(\S+)\s+(\S+)/) | 
| -			{ | 
| -				# Branch execution data | 
| -				push(@branches, scalar(@result) / 3); | 
| -				if ($1 eq "never") | 
| -				{ | 
| -					push(@branches, 0); | 
| -				} | 
| -				elsif ($2 eq "0%") | 
| -				{ | 
| -					push(@branches, 1); | 
| -				} | 
| -				else | 
| -				{ | 
| -					push(@branches, 2); | 
| -				} | 
| +			# Also remove CR from line-end | 
| +			s/\015$//; | 
| + | 
| +			if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) { | 
| +				# Block information - used to group related | 
| +				# branches | 
| +				$last_line = $2; | 
| +				$last_block = $3; | 
| +			} elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { | 
| +				next if (!$br_coverage); | 
| +				next if ($exclude_line); | 
| +				$branches = br_gvec_push($branches, $last_line, | 
| +						$last_block, $1, $2); | 
| +			} elsif (/^branch\s+(\d+)\s+never\s+executed/) { | 
| +				next if (!$br_coverage); | 
| +				next if ($exclude_line); | 
| +				$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; | 
| +				} | 
| push(@functions, $2, $1); | 
| } | 
| elsif (/^call/) | 
| @@ -1242,580 +1797,59 @@ sub read_gcov_file($) | 
| } | 
| elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) | 
| { | 
| +				my ($count, $line, $code) = ($1, $2, $3); | 
| + | 
| +				$last_line = $line; | 
| +				$last_block = $UNNAMED_BLOCK; | 
| +				# Check for exclusion markers | 
| +				if (!$no_markers) { | 
| +					if (/$EXCL_STOP/) { | 
| +						$exclude_flag = 0; | 
| +					} elsif (/$EXCL_START/) { | 
| +						$exclude_flag = 1; | 
| +					} | 
| +					if (/$EXCL_LINE/ || $exclude_flag) { | 
| +						$exclude_line = 1; | 
| +					} else { | 
| +						$exclude_line = 0; | 
| +					} | 
| +				} | 
| # <exec count>:<line number>:<source code> | 
| -				if ($2 eq "0") | 
| +				if ($line eq "0") | 
| { | 
| # Extra data | 
| } | 
| -				elsif ($1 eq "-") | 
| +				elsif ($count eq "-") | 
| { | 
| # Uninstrumented line | 
| push(@result, 0); | 
| push(@result, 0); | 
| -					push(@result, $3); | 
| +					push(@result, $code); | 
| } | 
| else | 
| { | 
| -					# Source code execution data | 
| -					$number = $1; | 
| - | 
| -					# Check for zero count | 
| -					if ($number eq "#####")	{ $number = 0; } | 
| - | 
| -					push(@result, 1); | 
| -					push(@result, $number); | 
| -					push(@result, $3); | 
| -				} | 
| -			} | 
| -		} | 
| -	} | 
| - | 
| -	close(INPUT); | 
| -	return(\@result, \@branches, \@functions); | 
| -} | 
| - | 
| - | 
| -# | 
| -# read_bb_file(bb_filename, base_dir) | 
| -# | 
| -# Read .bb file BB_FILENAME and return a hash containing the following | 
| -# mapping: | 
| -# | 
| -#   filename -> comma-separated list of pairs (function name=starting | 
| -#               line number) to indicate the starting line of a function or | 
| -#               =name to indicate an instrumented line | 
| -# | 
| -# for each entry in the .bb file. Filenames are absolute, i.e. relative | 
| -# filenames are prefixed with BASE_DIR. | 
| -# | 
| -# Die on error. | 
| -# | 
| - | 
| -sub read_bb_file($$) | 
| -{ | 
| -	my $bb_filename		= $_[0]; | 
| -	my $base_dir = $_[1]; | 
| -	my %result; | 
| -	my $filename; | 
| -	my $function_name; | 
| -	my $minus_one		= sprintf("%d", 0x80000001); | 
| -	my $minus_two		= sprintf("%d", 0x80000002); | 
| -	my $value; | 
| -	my $packed_word; | 
| -	local *INPUT; | 
| - | 
| -	open(INPUT, $bb_filename) | 
| -		or die("ERROR: cannot read $bb_filename!\n"); | 
| - | 
| -	binmode(INPUT); | 
| - | 
| -	# Read data in words of 4 bytes | 
| -	while (read(INPUT, $packed_word, 4) == 4) | 
| -	{ | 
| -		# Decode integer in intel byteorder | 
| -		$value = unpack_int32($packed_word, 0); | 
| - | 
| -		# Note: the .bb file format is documented in GCC info pages | 
| -		if ($value == $minus_one) | 
| -		{ | 
| -			# Filename follows | 
| -			$filename = read_string(*INPUT, $minus_one) | 
| -				or die("ERROR: incomplete filename in ". | 
| -				       "$bb_filename!\n"); | 
| - | 
| -			# Make path absolute | 
| -			$filename = solve_relative_path($base_dir, $filename); | 
| - | 
| -			# Insert into hash if not yet present. | 
| -			# This is necessary because functions declared as | 
| -			# "inline" are not listed as actual functions in | 
| -			# .bb files | 
| -			if (!$result{$filename}) | 
| -			{ | 
| -				$result{$filename}=""; | 
| -			} | 
| -		} | 
| -		elsif ($value == $minus_two) | 
| -		{ | 
| -			# Function name follows | 
| -			$function_name = read_string(*INPUT, $minus_two) | 
| -				 or die("ERROR: incomplete function ". | 
| -					"name in $bb_filename!\n"); | 
| -			$function_name =~ s/\W/_/g; | 
| -		} | 
| -		elsif ($value > 0) | 
| -		{ | 
| -			if (defined($filename)) | 
| -			{ | 
| -				$result{$filename} .= | 
| -					($result{$filename} ? "," : ""). | 
| -					"=$value"; | 
| -			} | 
| -			else | 
| -			{ | 
| -				warn("WARNING: unassigned line". | 
| -				     " number in .bb file ". | 
| -				     "$bb_filename\n"); | 
| -			} | 
| -			if ($function_name) | 
| -			{ | 
| -				# Got a full entry filename, funcname, lineno | 
| -				# Add to resulting hash | 
| - | 
| -				$result{$filename}.= | 
| -				  ($result{$filename} ? "," : ""). | 
| -				  join("=",($function_name,$value)); | 
| -				undef($function_name); | 
| -			} | 
| -		} | 
| -	} | 
| -	close(INPUT); | 
| - | 
| -	if (!scalar(keys(%result))) | 
| -	{ | 
| -		die("ERROR: no data found in $bb_filename!\n"); | 
| -	} | 
| -	return %result; | 
| -} | 
| - | 
| - | 
| -# | 
| -# read_string(handle, delimiter); | 
| -# | 
| -# Read and return a string in 4-byte chunks from HANDLE until DELIMITER | 
| -# is found. | 
| -# | 
| -# Return empty string on error. | 
| -# | 
| - | 
| -sub read_string(*$) | 
| -{ | 
| -	my $HANDLE	= $_[0]; | 
| -	my $delimiter	= $_[1]; | 
| -	my $string	= ""; | 
| -	my $packed_word; | 
| -	my $value; | 
| - | 
| -	while (read($HANDLE,$packed_word,4) == 4) | 
| -	{ | 
| -		$value = unpack_int32($packed_word, 0); | 
| - | 
| -		if ($value == $delimiter) | 
| -		{ | 
| -			# Remove trailing nil bytes | 
| -			$/="\0"; | 
| -			while (chomp($string)) {}; | 
| -			$/="\n"; | 
| -			return($string); | 
| -		} | 
| - | 
| -		$string = $string.$packed_word; | 
| -	} | 
| -	return(""); | 
| -} | 
| - | 
| - | 
| -# | 
| -# read_gcno_file(bb_filename, base_dir) | 
| -# | 
| -# Read .gcno file BB_FILENAME and return a hash containing the following | 
| -# mapping: | 
| -# | 
| -#   filename -> comma-separated list of pairs (function name=starting | 
| -#               line number) to indicate the starting line of a function or | 
| -#               =name to indicate an instrumented line | 
| -# | 
| -# for each entry in the .gcno file. Filenames are absolute, i.e. relative | 
| -# filenames are prefixed with BASE_DIR. | 
| -# | 
| -# Die on error. | 
| -# | 
| - | 
| -sub read_gcno_file($$) | 
| -{ | 
| -	my $gcno_filename	= $_[0]; | 
| -	my $base_dir = $_[1]; | 
| -	my %result; | 
| -	my $filename; | 
| -	my $function_name; | 
| -	my $lineno; | 
| -	my $length; | 
| -	my $value; | 
| -	my $endianness; | 
| -	my $blocks; | 
| -	my $packed_word; | 
| -	my $string; | 
| -	local *INPUT; | 
| - | 
| -	open(INPUT, $gcno_filename) | 
| -		or die("ERROR: cannot read $gcno_filename!\n"); | 
| - | 
| -	binmode(INPUT); | 
| - | 
| -	read(INPUT, $packed_word, 4) == 4 | 
| -		or die("ERROR: Invalid gcno file format\n"); | 
| - | 
| -	$value = unpack_int32($packed_word, 0); | 
| -	$endianness = !($value == $GCNO_FILE_MAGIC); | 
| - | 
| -	unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC | 
| -		or die("ERROR: gcno file magic does not match\n"); | 
| - | 
| -	seek(INPUT, 8, 1); | 
| - | 
| -	# Read data in words of 4 bytes | 
| -	while (read(INPUT, $packed_word, 4) == 4) | 
| -	{ | 
| -		# Decode integer in intel byteorder | 
| -		$value = unpack_int32($packed_word, $endianness); | 
| - | 
| -		if ($value == $GCNO_FUNCTION_TAG) | 
| -		{ | 
| -			# skip length, ident and checksum | 
| -			seek(INPUT, 12, 1); | 
| -			(undef, $function_name) = | 
| -				read_gcno_string(*INPUT, $endianness); | 
| -			$function_name =~ s/\W/_/g; | 
| -			(undef, $filename) = | 
| -				read_gcno_string(*INPUT, $endianness); | 
| -			$filename = solve_relative_path($base_dir, $filename); | 
| - | 
| -			read(INPUT, $packed_word, 4); | 
| -			$lineno = unpack_int32($packed_word, $endianness); | 
| - | 
| -			$result{$filename}.= | 
| -			    ($result{$filename} ? "," : ""). | 
| -				join("=",($function_name,$lineno)); | 
| -		} | 
| -		elsif ($value == $GCNO_LINES_TAG) | 
| -		{ | 
| -			# Check for names of files containing inlined code | 
| -			# included in this file | 
| -			read(INPUT, $packed_word, 4); | 
| -			$length = unpack_int32($packed_word, $endianness); | 
| -			if ($length > 0) | 
| -			{ | 
| -				# Block number | 
| -				read(INPUT, $packed_word, 4); | 
| -				$length--; | 
| -			} | 
| -			while ($length > 0) | 
| -			{ | 
| -				read(INPUT, $packed_word, 4); | 
| -				$lineno = unpack_int32($packed_word, | 
| -						       $endianness); | 
| -				$length--; | 
| -				if ($lineno != 0) | 
| -				{ | 
| -					if (defined($filename)) | 
| -					{ | 
| -						$result{$filename} .= | 
| -							($result{$filename} ? "," : ""). | 
| -							"=$lineno"; | 
| -					} | 
| -					else | 
| -					{ | 
| -						warn("WARNING: unassigned line". | 
| -						     " number in .gcno file ". | 
| -						     "$gcno_filename\n"); | 
| -					} | 
| -					next; | 
| -				} | 
| -				last if ($length == 0); | 
| -				($blocks, $string) = | 
| -					read_gcno_string(*INPUT, $endianness); | 
| -				if (defined($string)) | 
| -				{ | 
| -					$filename = $string; | 
| -				} | 
| -				if ($blocks > 1) | 
| -				{ | 
| -					$filename = solve_relative_path( | 
| -							$base_dir, $filename); | 
| -					if (!defined($result{$filename})) | 
| -					{ | 
| -						$result{$filename} = ""; | 
| +					if ($exclude_line) { | 
| +						push(@result, 0); | 
| +						push(@result, 0); | 
| +					} else { | 
| +						# Check for zero count | 
| +						if ($count eq "#####") { | 
| +							$count = 0; | 
| +						} | 
| +						push(@result, 1); | 
| +						push(@result, $count); | 
| } | 
| +					push(@result, $code); | 
| } | 
| -				$length -= $blocks; | 
| } | 
| } | 
| -		else | 
| -		{ | 
| -			read(INPUT, $packed_word, 4); | 
| -			$length = unpack_int32($packed_word, $endianness); | 
| -			seek(INPUT, 4 * $length, 1); | 
| -		} | 
| -	} | 
| -	close(INPUT); | 
| - | 
| -	if (!scalar(keys(%result))) | 
| -	{ | 
| -		die("ERROR: no data found in $gcno_filename!\n"); | 
| -	} | 
| -	return %result; | 
| -} | 
| - | 
| - | 
| -# | 
| -# read_gcno_string(handle, endianness); | 
| -# | 
| -# Read a string in 4-byte chunks from HANDLE. | 
| -# | 
| -# Return (number of 4-byte chunks read, string). | 
| -# | 
| - | 
| -sub read_gcno_string(*$) | 
| -{ | 
| -	my $handle		= $_[0]; | 
| -	my $endianness		= $_[1]; | 
| -	my $number_of_blocks	= 0; | 
| -	my $string		= ""; | 
| -	my $packed_word; | 
| - | 
| -	read($handle, $packed_word, 4) == 4 | 
| -		or die("ERROR: reading string\n"); | 
| - | 
| -	$number_of_blocks = unpack_int32($packed_word, $endianness); | 
| - | 
| -	if ($number_of_blocks == 0) | 
| -	{ | 
| -		return (1, undef); | 
| -	} | 
| - | 
| -	if (read($handle, $packed_word, 4 * $number_of_blocks) != | 
| -	     4 * $number_of_blocks) | 
| -	{ | 
| -		my $msg = "invalid string size ".(4 * $number_of_blocks)." in ". | 
| -			  "gcno file at position ".tell($handle)."\n"; | 
| -		if ($ignore[$ERROR_SOURCE]) | 
| -		{ | 
| -			warn("WARNING: $msg"); | 
| -			return (1, undef); | 
| -		} | 
| -		else | 
| -		{ | 
| -			die("ERROR: $msg"); | 
| -		} | 
| } | 
|  | 
| -	$string = $string . $packed_word; | 
| - | 
| -	# Remove trailing nil bytes | 
| -	$/="\0"; | 
| -	while (chomp($string)) {}; | 
| -	$/="\n"; | 
| - | 
| -	return(1 + $number_of_blocks, $string); | 
| -} | 
| - | 
| - | 
| -# | 
| -# read_hammer_bbg_file(bb_filename, base_dir) | 
| -# | 
| -# Read .bbg file BB_FILENAME and return a hash containing the following | 
| -# mapping: | 
| -# | 
| -#   filename -> comma-separated list of pairs (function name=starting | 
| -#               line number) to indicate the starting line of a function or | 
| -#               =name to indicate an instrumented line | 
| -# | 
| -# for each entry in the .bbg file. Filenames are absolute, i.e. relative | 
| -# filenames are prefixed with BASE_DIR. | 
| -# | 
| -# Die on error. | 
| -# | 
| - | 
| -sub read_hammer_bbg_file($$) | 
| -{ | 
| -	my $bbg_filename = $_[0]; | 
| -	my $base_dir = $_[1]; | 
| -	my %result; | 
| -	my $filename; | 
| -	my $function_name; | 
| -	my $first_line; | 
| -	my $lineno; | 
| -	my $length; | 
| -	my $value; | 
| -	my $endianness; | 
| -	my $blocks; | 
| -	my $packed_word; | 
| -	local *INPUT; | 
| - | 
| -	open(INPUT, $bbg_filename) | 
| -		or die("ERROR: cannot read $bbg_filename!\n"); | 
| - | 
| -	binmode(INPUT); | 
| - | 
| -	# Read magic | 
| -	read(INPUT, $packed_word, 4) == 4 | 
| -		or die("ERROR: invalid bbg file format\n"); | 
| - | 
| -	$endianness = 1; | 
| - | 
| -	unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC | 
| -		or die("ERROR: bbg file magic does not match\n"); | 
| - | 
| -	# Skip version | 
| -	seek(INPUT, 4, 1); | 
| - | 
| -	# Read data in words of 4 bytes | 
| -	while (read(INPUT, $packed_word, 4) == 4) | 
| -	{ | 
| -		# Get record tag | 
| -		$value = unpack_int32($packed_word, $endianness); | 
| - | 
| -		# Get record length | 
| -		read(INPUT, $packed_word, 4); | 
| -		$length = unpack_int32($packed_word, $endianness); | 
| - | 
| -		if ($value == $GCNO_FUNCTION_TAG) | 
| -		{ | 
| -			# Get function name | 
| -			($value, $function_name) = | 
| -				read_hammer_bbg_string(*INPUT, $endianness); | 
| -			$function_name =~ s/\W/_/g; | 
| -			$filename = undef; | 
| -			$first_line = undef; | 
| - | 
| -			seek(INPUT, $length - $value * 4, 1); | 
| -		} | 
| -		elsif ($value == $GCNO_LINES_TAG) | 
| -		{ | 
| -			# Get linenumber and filename | 
| -			# Skip block number | 
| -			seek(INPUT, 4, 1); | 
| -			$length -= 4; | 
| - | 
| -			while ($length > 0) | 
| -			{ | 
| -				read(INPUT, $packed_word, 4); | 
| -				$lineno = unpack_int32($packed_word, | 
| -						       $endianness); | 
| -				$length -= 4; | 
| -				if ($lineno != 0) | 
| -				{ | 
| -					if (!defined($first_line)) | 
| -					{ | 
| -						$first_line = $lineno; | 
| -					} | 
| -					if (defined($filename)) | 
| -					{ | 
| -						$result{$filename} .= | 
| -							($result{$filename} ? "," : ""). | 
| -							"=$lineno"; | 
| -					} | 
| -					else | 
| -					{ | 
| -						warn("WARNING: unassigned line". | 
| -						     " number in .bbg file ". | 
| -						     "$bbg_filename\n"); | 
| -					} | 
| -					next; | 
| -				} | 
| -				($blocks, $value) = | 
| -					read_hammer_bbg_string( | 
| -						*INPUT, $endianness); | 
| -				# Add all filenames to result list | 
| -				if (defined($value)) | 
| -				{ | 
| -					$value = solve_relative_path( | 
| -							$base_dir, $value); | 
| -					if (!defined($result{$value})) | 
| -					{ | 
| -						$result{$value} = undef; | 
| -					} | 
| -					if (!defined($filename)) | 
| -					{ | 
| -						$filename = $value; | 
| -					} | 
| -				} | 
| -				$length -= $blocks * 4; | 
| - | 
| -				# Got a complete data set? | 
| -				if (defined($filename) && | 
| -				    defined($first_line) && | 
| -				    defined($function_name)) | 
| -				{ | 
| -					# Add it to our result hash | 
| -					if (defined($result{$filename})) | 
| -					{ | 
| -						$result{$filename} .= | 
| -						",$function_name=$first_line"; | 
| -					} | 
| -					else | 
| -					{ | 
| -						$result{$filename} = | 
| -						"$function_name=$first_line"; | 
| -					} | 
| -					$function_name = undef; | 
| -					$filename = undef; | 
| -					$first_line = undef; | 
| -				} | 
| -			} | 
| -		} | 
| -		else | 
| -		{ | 
| -			# Skip other records | 
| -			seek(INPUT, $length, 1); | 
| -		} | 
| -	} | 
| close(INPUT); | 
| - | 
| -	if (!scalar(keys(%result))) | 
| -	{ | 
| -		die("ERROR: no data found in $bbg_filename!\n"); | 
| -	} | 
| -	return %result; | 
| -} | 
| - | 
| - | 
| -# | 
| -# read_hammer_bbg_string(handle, endianness); | 
| -# | 
| -# Read a string in 4-byte chunks from HANDLE. | 
| -# | 
| -# Return (number of 4-byte chunks read, string). | 
| -# | 
| - | 
| -sub read_hammer_bbg_string(*$) | 
| -{ | 
| -	my $handle		= $_[0]; | 
| -	my $endianness		= $_[1]; | 
| -	my $length		= 0; | 
| -	my $string		= ""; | 
| -	my $packed_word; | 
| -	my $pad; | 
| - | 
| -	read($handle, $packed_word, 4) == 4 | 
| -		or die("ERROR: reading string\n"); | 
| - | 
| -	$length = unpack_int32($packed_word, $endianness); | 
| -	$pad = 4 - $length % 4; | 
| - | 
| -	if ($length == 0) | 
| -	{ | 
| -		return (1, undef); | 
| +	if ($exclude_flag) { | 
| +		warn("WARNING: unterminated exclusion section in $filename\n"); | 
| } | 
| - | 
| -	read($handle, $string, $length) == | 
| -		$length or die("ERROR: reading string\n"); | 
| -	seek($handle, $pad, 1); | 
| - | 
| -	return(1 + ($length + $pad) / 4, $string); | 
| -} | 
| - | 
| -# | 
| -# unpack_int32(word, endianness) | 
| -# | 
| -# Interpret 4-byte binary string WORD as signed 32 bit integer in | 
| -# endian encoding defined by ENDIANNESS (0=little, 1=big) and return its | 
| -# value. | 
| -# | 
| - | 
| -sub unpack_int32($$) | 
| -{ | 
| -	return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0])); | 
| +	return(\@result, $branches, \@functions); | 
| } | 
|  | 
|  | 
| @@ -1831,7 +1865,7 @@ sub get_gcov_version() | 
| my $version_string; | 
| my $result; | 
|  | 
| -	open(GCOV_PIPE, "$gcov_tool -v |") | 
| +	open(GCOV_PIPE, "-|", "$gcov_tool -v") | 
| or die("ERROR: cannot retrieve gcov version!\n"); | 
| $version_string = <GCOV_PIPE>; | 
| close(GCOV_PIPE); | 
| @@ -1850,13 +1884,7 @@ sub get_gcov_version() | 
| $result = $1 << 16 | $2 << 8; | 
| } | 
| } | 
| -        if ($version_string =~ /suse/i && $result == 0x30303 || | 
| -            $version_string =~ /mandrake/i && $result == 0x30302) | 
| -	{ | 
| -		info("Using compatibility mode for GCC 3.3 (hammer)\n"); | 
| -		$compatibility = $COMPAT_HAMMER; | 
| -	} | 
| -	return $result; | 
| +	return ($result, $version_string); | 
| } | 
|  | 
|  | 
| @@ -1919,13 +1947,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 = $?; | 
|  | 
| @@ -1934,8 +1963,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; | 
| } | 
| @@ -1956,7 +1985,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; | 
| @@ -1995,8 +2024,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($) | 
| @@ -2005,51 +2034,175 @@ sub apply_config($) | 
|  | 
| foreach (keys(%{$ref})) | 
| { | 
| -		if (defined($config->{$_})) | 
| -		{ | 
| +		if (defined($opt_rc{$_})) { | 
| +			${$ref->{$_}} = $opt_rc{$_}; | 
| +		} elsif (defined($config->{$_})) { | 
| ${$ref->{$_}} = $config->{$_}; | 
| } | 
| } | 
| } | 
|  | 
|  | 
| -sub gen_initial_info($) | 
| +# | 
| +# get_exclusion_data(filename) | 
| +# | 
| +# Scan specified source code file for exclusion markers and return | 
| +#   linenumber -> 1 | 
| +# for all lines which should be excluded. | 
| +# | 
| + | 
| +sub get_exclusion_data($) | 
| { | 
| -	my $directory = $_[0]; | 
| -	my @file_list; | 
| +	my ($filename) = @_; | 
| +	my %list; | 
| +	my $flag = 0; | 
| +	local *HANDLE; | 
|  | 
| -	if (-d $directory) | 
| -	{ | 
| -		info("Scanning $directory for $graph_file_extension ". | 
| -		     "files ...\n"); | 
| +	if (!open(HANDLE, "<", $filename)) { | 
| +		warn("WARNING: could not open $filename\n"); | 
| +		return undef; | 
| +	} | 
| +	while (<HANDLE>) { | 
| +		if (/$EXCL_STOP/) { | 
| +			$flag = 0; | 
| +		} elsif (/$EXCL_START/) { | 
| +			$flag = 1; | 
| +		} | 
| +		if (/$EXCL_LINE/ || $flag) { | 
| +			$list{$.} = 1; | 
| +		} | 
| +	} | 
| +	close(HANDLE); | 
|  | 
| -		@file_list = `find "$directory" $maxdepth $follow -name \\*$graph_file_extension -type f 2>/dev/null`; | 
| -		chomp(@file_list); | 
| -		@file_list or die("ERROR: no $graph_file_extension files ". | 
| -				  "found in $directory!\n"); | 
| -		info("Found %d graph files in %s\n", $#file_list+1, $directory); | 
| +	if ($flag) { | 
| +		warn("WARNING: unterminated exclusion section in $filename\n"); | 
| } | 
| -	else | 
| -	{ | 
| -		@file_list = ($directory); | 
| + | 
| +	return \%list; | 
| +} | 
| + | 
| + | 
| +# | 
| +# apply_exclusion_data(instr, graph) | 
| +# | 
| +# Remove lines from instr and graph data structures which are marked | 
| +# for exclusion in the source code file. | 
| +# | 
| +# Return adjusted (instr, graph). | 
| +# | 
| +# graph         : file name -> function data | 
| +# function data : function name -> line data | 
| +# line data     : [ line1, line2, ... ] | 
| +# | 
| +# instr     : filename -> line data | 
| +# line data : [ line1, line2, ... ] | 
| +# | 
| + | 
| +sub apply_exclusion_data($$) | 
| +{ | 
| +	my ($instr, $graph) = @_; | 
| +	my $filename; | 
| +	my %excl_data; | 
| +	my $excl_read_failed = 0; | 
| + | 
| +	# Collect exclusion marker data | 
| +	foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) { | 
| +		my $excl = get_exclusion_data($filename); | 
| + | 
| +		# Skip and note if file could not be read | 
| +		if (!defined($excl)) { | 
| +			$excl_read_failed = 1; | 
| +			next; | 
| +		} | 
| + | 
| +		# Add to collection if there are markers | 
| +		$excl_data{$filename} = $excl if (keys(%{$excl}) > 0); | 
| } | 
|  | 
| -	# Process all files in list | 
| -	foreach (@file_list) { process_graphfile($_); } | 
| +	# Warn if not all source files could be read | 
| +	if ($excl_read_failed) { | 
| +		warn("WARNING: some exclusion markers may be ignored\n"); | 
| +	} | 
| + | 
| +	# Skip if no markers were found | 
| +	return ($instr, $graph) if (keys(%excl_data) == 0); | 
| + | 
| +	# Apply exclusion marker data to graph | 
| +	foreach $filename (keys(%excl_data)) { | 
| +		my $function_data = $graph->{$filename}; | 
| +		my $excl = $excl_data{$filename}; | 
| +		my $function; | 
| + | 
| +		next if (!defined($function_data)); | 
| + | 
| +		foreach $function (keys(%{$function_data})) { | 
| +			my $line_data = $function_data->{$function}; | 
| +			my $line; | 
| +			my @new_data; | 
| + | 
| +			# To be consistent with exclusion parser in non-initial | 
| +			# case we need to remove a function if the first line | 
| +			# was excluded | 
| +			if ($excl->{$line_data->[0]}) { | 
| +				delete($function_data->{$function}); | 
| +				next; | 
| +			} | 
| +			# Copy only lines which are not excluded | 
| +			foreach $line (@{$line_data}) { | 
| +				push(@new_data, $line) if (!$excl->{$line}); | 
| +			} | 
| + | 
| +			# Store modified list | 
| +			if (scalar(@new_data) > 0) { | 
| +				$function_data->{$function} = \@new_data; | 
| +			} else { | 
| +				# All of this function was excluded | 
| +				delete($function_data->{$function}); | 
| +			} | 
| +		} | 
| + | 
| +		# Check if all functions of this file were excluded | 
| +		if (keys(%{$function_data}) == 0) { | 
| +			delete($graph->{$filename}); | 
| +		} | 
| +	} | 
| + | 
| +	# Apply exclusion marker data to instr | 
| +	foreach $filename (keys(%excl_data)) { | 
| +		my $line_data = $instr->{$filename}; | 
| +		my $excl = $excl_data{$filename}; | 
| +		my $line; | 
| +		my @new_data; | 
| + | 
| +		next if (!defined($line_data)); | 
| + | 
| +		# Copy only lines which are not excluded | 
| +		foreach $line (@{$line_data}) { | 
| +			push(@new_data, $line) if (!$excl->{$line}); | 
| +		} | 
| + | 
| +		# Store modified list | 
| +		$instr->{$filename} = \@new_data; | 
| +	} | 
| + | 
| +	return ($instr, $graph); | 
| } | 
|  | 
| -sub process_graphfile($) | 
| + | 
| +sub process_graphfile($$) | 
| { | 
| -	my $graph_filename = $_[0]; | 
| +	my ($file, $dir) = @_; | 
| +	my $graph_filename = $file; | 
| my $graph_dir; | 
| my $graph_basename; | 
| my $source_dir; | 
| my $base_dir; | 
| -	my %graph_data; | 
| +	my $graph; | 
| +	my $instr; | 
| my $filename; | 
| local *INFO_HANDLE; | 
|  | 
| -	info("Processing $_[0]\n"); | 
| +	info("Processing %s\n", abs2rel($file, $dir)); | 
|  | 
| # Get path to data file in absolute and normalized form (begins with /, | 
| # contains no more ../ or ./) | 
| @@ -2058,11 +2211,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 | 
| @@ -2077,19 +2229,30 @@ sub process_graphfile($) | 
|  | 
| if ($gcov_version < $GCOV_VERSION_3_4_0) | 
| { | 
| -		if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) | 
| +		if (is_compat($COMPAT_MODE_HAMMER)) | 
| { | 
| -			%graph_data = read_hammer_bbg_file($graph_filename, | 
| -							   $base_dir); | 
| +			($instr, $graph) = read_bbg($graph_filename); | 
| } | 
| else | 
| { | 
| -			%graph_data = read_bb_file($graph_filename, $base_dir); | 
| +			($instr, $graph) = read_bb($graph_filename); | 
| } | 
| } | 
| else | 
| { | 
| -		%graph_data = read_gcno_file($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); | 
| } | 
|  | 
| # Check whether we're writing to a single file | 
| @@ -2102,7 +2265,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"); | 
| } | 
| @@ -2110,51 +2273,51 @@ 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"); | 
| } | 
|  | 
| # Write test name | 
| printf(INFO_HANDLE "TN:%s\n", $test_name); | 
| -	foreach $filename (keys(%graph_data)) | 
| +	foreach $filename (sort(keys(%{$instr}))) | 
| { | 
| -		my %lines; | 
| -		my $count = 0; | 
| -		my @functions; | 
| +		my $funcdata = $graph->{$filename}; | 
| +		my $line; | 
| +		my $linedata; | 
|  | 
| print(INFO_HANDLE "SF:$filename\n"); | 
|  | 
| -		# Write function related data | 
| -		foreach (split(",",$graph_data{$filename})) | 
| -		{ | 
| -			my ($fn, $line) = split("=", $_); | 
| - | 
| -			if ($fn eq "") | 
| -			{ | 
| -				$lines{$line} = ""; | 
| -				next; | 
| -			} | 
| +		if (defined($funcdata) && $func_coverage) { | 
| +			my @functions = sort {$funcdata->{$a}->[0] <=> | 
| +					      $funcdata->{$b}->[0]} | 
| +					     keys(%{$funcdata}); | 
| +			my $func; | 
|  | 
| -			# Normalize function name | 
| -			$fn =~ s/\W/_/g; | 
| +			# Gather list of instrumented lines and functions | 
| +			foreach $func (@functions) { | 
| +				$linedata = $funcdata->{$func}; | 
|  | 
| -			print(INFO_HANDLE "FN:$line,$fn\n"); | 
| -			push(@functions, $fn); | 
| -		} | 
| -		foreach (@functions) { | 
| -			print(INFO_HANDLE "FNDA:$_,0\n"); | 
| +				# Print function name and starting line | 
| +				print(INFO_HANDLE "FN:".$linedata->[0]. | 
| +				      ",".filter_fn_name($func)."\n"); | 
| +			} | 
| +			# Print zero function coverage data | 
| +			foreach $func (@functions) { | 
| +				print(INFO_HANDLE "FNDA:0,". | 
| +				      filter_fn_name($func)."\n"); | 
| +			} | 
| +			# Print function summary | 
| +			print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); | 
| +			print(INFO_HANDLE "FNH:0\n"); | 
| } | 
| -		print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); | 
| -		print(INFO_HANDLE "FNH:0\n"); | 
| - | 
| -		# Write line related data | 
| -		foreach (sort {$a <=> $b } keys(%lines)) | 
| -		{ | 
| -			print(INFO_HANDLE "DA:$_,0\n"); | 
| -			$count++; | 
| +		# Print zero line coverage data | 
| +		foreach $line (@{$instr->{$filename}}) { | 
| +			print(INFO_HANDLE "DA:$line,0\n"); | 
| } | 
| +		# Print line summary | 
| +		print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n"); | 
| print(INFO_HANDLE "LH:0\n"); | 
| -		print(INFO_HANDLE "LF:$count\n"); | 
| + | 
| print(INFO_HANDLE "end_of_record\n"); | 
| } | 
| if (!($output_filename && ($output_filename eq "-"))) | 
| @@ -2163,6 +2326,16 @@ sub process_graphfile($) | 
| } | 
| } | 
|  | 
| +sub filter_fn_name($) | 
| +{ | 
| +	my ($fn) = @_; | 
| + | 
| +	# Remove characters used internally as function name delimiters | 
| +	$fn =~ s/[,=]/_/g; | 
| + | 
| +	return $fn; | 
| +} | 
| + | 
| sub warn_handler($) | 
| { | 
| my ($msg) = @_; | 
| @@ -2176,3 +2349,1309 @@ sub die_handler($) | 
|  | 
| die("$tool_name: $msg"); | 
| } | 
| + | 
| + | 
| +# | 
| +# graph_error(filename, message) | 
| +# | 
| +# Print message about error in graph file. If ignore_graph_error is set, return. | 
| +# Otherwise abort. | 
| +# | 
| + | 
| +sub graph_error($$) | 
| +{ | 
| +	my ($filename, $msg) = @_; | 
| + | 
| +	if ($ignore[$ERROR_GRAPH]) { | 
| +		warn("WARNING: $filename: $msg - skipping\n"); | 
| +		return; | 
| +	} | 
| +	die("ERROR: $filename: $msg\n"); | 
| +} | 
| + | 
| +# | 
| +# graph_expect(description) | 
| +# | 
| +# If debug is set to a non-zero value, print the specified description of what | 
| +# is expected to be read next from the graph file. | 
| +# | 
| + | 
| +sub graph_expect($) | 
| +{ | 
| +	my ($msg) = @_; | 
| + | 
| +	if (!$debug || !defined($msg)) { | 
| +		return; | 
| +	} | 
| + | 
| +	print(STDERR "DEBUG: expecting $msg\n"); | 
| +} | 
| + | 
| +# | 
| +# graph_read(handle, bytes[, description, peek]) | 
| +# | 
| +# Read and return the specified number of bytes from handle. Return undef | 
| +# if the number of bytes could not be read. If PEEK is non-zero, reset | 
| +# file position after read. | 
| +# | 
| + | 
| +sub graph_read(*$;$$) | 
| +{ | 
| +	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: $op($length)=$result: "); | 
| +		for ($i = 0; $i < length($data); $i++) { | 
| +			my $c = substr($data, $i, 1);; | 
| +			my $n = ord($c); | 
| + | 
| +			$hex .= sprintf("%02x ", $n); | 
| +			if ($n >= 32 && $n <= 127) { | 
| +				$ascii .= $c; | 
| +			} else { | 
| +				$ascii .= "."; | 
| +			} | 
| +		} | 
| +		print(STDERR "$hex |$ascii|"); | 
| +		print(STDERR "\n"); | 
| +	} | 
| +	if ($peek) { | 
| +		if (!seek($handle, $pos, 0)) { | 
| +			warn("Could not set file position: $!\n"); | 
| +			return undef; | 
| +		} | 
| +	} | 
| +	if ($result != $length) { | 
| +		return undef; | 
| +	} | 
| +	return $data; | 
| +} | 
| + | 
| +# | 
| +# graph_skip(handle, bytes[, description]) | 
| +# | 
| +# Read and discard the specified number of bytes from handle. Return non-zero | 
| +# if bytes could be read, zero otherwise. | 
| +# | 
| + | 
| +sub graph_skip(*$;$) | 
| +{ | 
| +	my ($handle, $length, $desc) = @_; | 
| + | 
| +	if (defined(graph_read($handle, $length, $desc))) { | 
| +		return 1; | 
| +	} | 
| +	return 0; | 
| +} | 
| + | 
| +# | 
| +# sort_uniq(list) | 
| +# | 
| +# Return list in numerically ascending order and without duplicate entries. | 
| +# | 
| + | 
| +sub sort_uniq(@) | 
| +{ | 
| +	my (@list) = @_; | 
| +	my %hash; | 
| + | 
| +	foreach (@list) { | 
| +		$hash{$_} = 1; | 
| +	} | 
| +	return sort { $a <=> $b } keys(%hash); | 
| +} | 
| + | 
| +# | 
| +# sort_uniq_lex(list) | 
| +# | 
| +# Return list in lexically ascending order and without duplicate entries. | 
| +# | 
| + | 
| +sub sort_uniq_lex(@) | 
| +{ | 
| +	my (@list) = @_; | 
| +	my %hash; | 
| + | 
| +	foreach (@list) { | 
| +		$hash{$_} = 1; | 
| +	} | 
| +	return sort keys(%hash); | 
| +} | 
| + | 
| +# | 
| +# 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. | 
| +# Sort list of line numbers numerically ascending. | 
| +# | 
| + | 
| +sub graph_cleanup($) | 
| +{ | 
| +	my ($graph) = @_; | 
| +	my $filename; | 
| + | 
| +	foreach $filename (keys(%{$graph})) { | 
| +		my $per_file = $graph->{$filename}; | 
| +		my $function; | 
| + | 
| +		foreach $function (keys(%{$per_file})) { | 
| +			my $lines = $per_file->{$function}; | 
| + | 
| +			if (scalar(@$lines) == 0) { | 
| +				# Remove empty function | 
| +				delete($per_file->{$function}); | 
| +				next; | 
| +			} | 
| +			# Normalize list | 
| +			$per_file->{$function} = [ sort_uniq(@$lines) ]; | 
| +		} | 
| +		if (scalar(keys(%{$per_file})) == 0) { | 
| +			# Remove empty file | 
| +			delete($graph->{$filename}); | 
| +		} | 
| +	} | 
| +} | 
| + | 
| +# | 
| +# graph_find_base(bb) | 
| +# | 
| +# Try to identify the filename which is the base source file for the | 
| +# specified bb data. | 
| +# | 
| + | 
| +sub graph_find_base($) | 
| +{ | 
| +	my ($bb) = @_; | 
| +	my %file_count; | 
| +	my $basefile; | 
| +	my $file; | 
| +	my $func; | 
| +	my $filedata; | 
| +	my $count; | 
| +	my $num; | 
| + | 
| +	# Identify base name for this bb data. | 
| +	foreach $func (keys(%{$bb})) { | 
| +		$filedata = $bb->{$func}; | 
| + | 
| +		foreach $file (keys(%{$filedata})) { | 
| +			$count = $file_count{$file}; | 
| + | 
| +			# Count file occurrence | 
| +			$file_count{$file} = defined($count) ? $count + 1 : 1; | 
| +		} | 
| +	} | 
| +	$count = 0; | 
| +	$num = 0; | 
| +	foreach $file (keys(%file_count)) { | 
| +		if ($file_count{$file} > $count) { | 
| +			# The file that contains code for the most functions | 
| +			# is likely the base file | 
| +			$count = $file_count{$file}; | 
| +			$num = 1; | 
| +			$basefile = $file; | 
| +		} elsif ($file_count{$file} == $count) { | 
| +			# If more than one file could be the basefile, we | 
| +			# don't have a basefile | 
| +			$basefile = undef; | 
| +		} | 
| +	} | 
| + | 
| +	return $basefile; | 
| +} | 
| + | 
| +# | 
| +# graph_from_bb(bb, fileorder, bb_filename) | 
| +# | 
| +# Convert data from bb to the graph format and list of instrumented lines. | 
| +# Returns (instr, graph). | 
| +# | 
| +# bb         : function name -> file data | 
| +#            : undef -> file order | 
| +# file data  : filename -> line data | 
| +# line data  : [ line1, line2, ... ] | 
| +# | 
| +# file order : function name -> [ filename1, filename2, ... ] | 
| +# | 
| +# graph         : file name -> function data | 
| +# function data : function name -> line data | 
| +# line data     : [ line1, line2, ... ] | 
| +# | 
| +# instr     : filename -> line data | 
| +# line data : [ line1, line2, ... ] | 
| +# | 
| + | 
| +sub graph_from_bb($$$) | 
| +{ | 
| +	my ($bb, $fileorder, $bb_filename) = @_; | 
| +	my $graph = {}; | 
| +	my $instr = {}; | 
| +	my $basefile; | 
| +	my $file; | 
| +	my $func; | 
| +	my $filedata; | 
| +	my $linedata; | 
| +	my $order; | 
| + | 
| +	$basefile = graph_find_base($bb); | 
| +	# Create graph structure | 
| +	foreach $func (keys(%{$bb})) { | 
| +		$filedata = $bb->{$func}; | 
| +		$order = $fileorder->{$func}; | 
| + | 
| +		# Account for lines in functions | 
| +		if (defined($basefile) && defined($filedata->{$basefile})) { | 
| +			# If the basefile contributes to this function, | 
| +			# account this function to the basefile. | 
| +			$graph->{$basefile}->{$func} = $filedata->{$basefile}; | 
| +		} else { | 
| +			# If the basefile does not contribute to this function, | 
| +			# account this function to the first file contributing | 
| +			# lines. | 
| +			$graph->{$order->[0]}->{$func} = | 
| +				$filedata->{$order->[0]}; | 
| +		} | 
| + | 
| +		foreach $file (keys(%{$filedata})) { | 
| +			# Account for instrumented lines | 
| +			$linedata = $filedata->{$file}; | 
| +			push(@{$instr->{$file}}, @$linedata); | 
| +		} | 
| +	} | 
| +	# Clean up array of instrumented lines | 
| +	foreach $file (keys(%{$instr})) { | 
| +		$instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ]; | 
| +	} | 
| + | 
| +	return ($instr, $graph); | 
| +} | 
| + | 
| +# | 
| +# graph_add_order(fileorder, function, filename) | 
| +# | 
| +# Add an entry for filename to the fileorder data set for function. | 
| +# | 
| + | 
| +sub graph_add_order($$$) | 
| +{ | 
| +	my ($fileorder, $function, $filename) = @_; | 
| +	my $item; | 
| +	my $list; | 
| + | 
| +	$list = $fileorder->{$function}; | 
| +	foreach $item (@$list) { | 
| +		if ($item eq $filename) { | 
| +			return; | 
| +		} | 
| +	} | 
| +	push(@$list, $filename); | 
| +	$fileorder->{$function} = $list; | 
| +} | 
| + | 
| +# | 
| +# read_bb_word(handle[, description]) | 
| +# | 
| +# Read and return a word in .bb format from handle. | 
| +# | 
| + | 
| +sub read_bb_word(*;$) | 
| +{ | 
| +	my ($handle, $desc) = @_; | 
| + | 
| +	return graph_read($handle, 4, $desc); | 
| +} | 
| + | 
| +# | 
| +# read_bb_value(handle[, description]) | 
| +# | 
| +# Read a word in .bb format from handle and return the word and its integer | 
| +# value. | 
| +# | 
| + | 
| +sub read_bb_value(*;$) | 
| +{ | 
| +	my ($handle, $desc) = @_; | 
| +	my $word; | 
| + | 
| +	$word = read_bb_word($handle, $desc); | 
| +	return undef if (!defined($word)); | 
| + | 
| +	return ($word, unpack("V", $word)); | 
| +} | 
| + | 
| +# | 
| +# read_bb_string(handle, delimiter) | 
| +# | 
| +# Read and return a string in .bb format from handle up to the specified | 
| +# delimiter value. | 
| +# | 
| + | 
| +sub read_bb_string(*$) | 
| +{ | 
| +	my ($handle, $delimiter) = @_; | 
| +	my $word; | 
| +	my $value; | 
| +	my $string = ""; | 
| + | 
| +	graph_expect("string"); | 
| +	do { | 
| +		($word, $value) = read_bb_value($handle, "string or delimiter"); | 
| +		return undef if (!defined($value)); | 
| +		if ($value != $delimiter) { | 
| +			$string .= $word; | 
| +		} | 
| +	} while ($value != $delimiter); | 
| +	$string =~ s/\0//g; | 
| + | 
| +	return $string; | 
| +} | 
| + | 
| +# | 
| +# read_bb(filename) | 
| +# | 
| +# Read the contents of the specified .bb file and return (instr, graph), where: | 
| +# | 
| +#   instr     : filename -> line data | 
| +#   line data : [ line1, line2, ... ] | 
| +# | 
| +#   graph     :     filename -> file_data | 
| +#   file_data : function name -> line_data | 
| +#   line_data : [ line1, line2, ... ] | 
| +# | 
| +# See the gcov info pages of gcc 2.95 for a description of the .bb file format. | 
| +# | 
| + | 
| +sub read_bb($) | 
| +{ | 
| +	my ($bb_filename) = @_; | 
| +	my $minus_one = 0x80000001; | 
| +	my $minus_two = 0x80000002; | 
| +	my $value; | 
| +	my $filename; | 
| +	my $function; | 
| +	my $bb = {}; | 
| +	my $fileorder = {}; | 
| +	my $instr; | 
| +	my $graph; | 
| +	local *HANDLE; | 
| + | 
| +	open(HANDLE, "<", $bb_filename) or goto open_error; | 
| +	binmode(HANDLE); | 
| +	while (!eof(HANDLE)) { | 
| +		$value = read_bb_value(*HANDLE, "data word"); | 
| +		goto incomplete if (!defined($value)); | 
| +		if ($value == $minus_one) { | 
| +			# Source file name | 
| +			graph_expect("filename"); | 
| +			$filename = read_bb_string(*HANDLE, $minus_one); | 
| +			goto incomplete if (!defined($filename)); | 
| +		} elsif ($value == $minus_two) { | 
| +			# Function name | 
| +			graph_expect("function name"); | 
| +			$function = read_bb_string(*HANDLE, $minus_two); | 
| +			goto incomplete if (!defined($function)); | 
| +		} elsif ($value > 0) { | 
| +			# Line number | 
| +			if (!defined($filename) || !defined($function)) { | 
| +				warn("WARNING: unassigned line number ". | 
| +				     "$value\n"); | 
| +				next; | 
| +			} | 
| +			push(@{$bb->{$function}->{$filename}}, $value); | 
| +			graph_add_order($fileorder, $function, $filename); | 
| +		} | 
| +	} | 
| +	close(HANDLE); | 
| +	($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename); | 
| +	graph_cleanup($graph); | 
| + | 
| +	return ($instr, $graph); | 
| + | 
| +open_error: | 
| +	graph_error($bb_filename, "could not open file"); | 
| +	return undef; | 
| +incomplete: | 
| +	graph_error($bb_filename, "reached unexpected end of file"); | 
| +	return undef; | 
| +} | 
| + | 
| +# | 
| +# read_bbg_word(handle[, description]) | 
| +# | 
| +# Read and return a word in .bbg format. | 
| +# | 
| + | 
| +sub read_bbg_word(*;$) | 
| +{ | 
| +	my ($handle, $desc) = @_; | 
| + | 
| +	return graph_read($handle, 4, $desc); | 
| +} | 
| + | 
| +# | 
| +# read_bbg_value(handle[, description]) | 
| +# | 
| +# Read a word in .bbg format from handle and return its integer value. | 
| +# | 
| + | 
| +sub read_bbg_value(*;$) | 
| +{ | 
| +	my ($handle, $desc) = @_; | 
| +	my $word; | 
| + | 
| +	$word = read_bbg_word($handle, $desc); | 
| +	return undef if (!defined($word)); | 
| + | 
| +	return unpack("N", $word); | 
| +} | 
| + | 
| +# | 
| +# read_bbg_string(handle) | 
| +# | 
| +# Read and return a string in .bbg format. | 
| +# | 
| + | 
| +sub read_bbg_string(*) | 
| +{ | 
| +	my ($handle, $desc) = @_; | 
| +	my $length; | 
| +	my $string; | 
| + | 
| +	graph_expect("string"); | 
| +	# Read string length | 
| +	$length = read_bbg_value($handle, "string length"); | 
| +	return undef if (!defined($length)); | 
| +	if ($length == 0) { | 
| +		return ""; | 
| +	} | 
| +	# Read string | 
| +	$string = graph_read($handle, $length, "string"); | 
| +	return undef if (!defined($string)); | 
| +	# Skip padding | 
| +	graph_skip($handle, 4 - $length % 4, "string padding") or return undef; | 
| + | 
| +	return $string; | 
| +} | 
| + | 
| +# | 
| +# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, | 
| +#                       function) | 
| +# | 
| +# Read a bbg format lines record from handle and add the relevant data to | 
| +# bb and fileorder. Return filename on success, undef on error. | 
| +# | 
| + | 
| +sub read_bbg_lines_record(*$$$$$) | 
| +{ | 
| +	my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_; | 
| +	my $string; | 
| +	my $lineno; | 
| + | 
| +	graph_expect("lines record"); | 
| +	# Skip basic block index | 
| +	graph_skip($handle, 4, "basic block index") or return undef; | 
| +	while (1) { | 
| +		# Read line number | 
| +		$lineno = read_bbg_value($handle, "line number"); | 
| +		return undef if (!defined($lineno)); | 
| +		if ($lineno == 0) { | 
| +			# Got a marker for a new filename | 
| +			graph_expect("filename"); | 
| +			$string = read_bbg_string($handle); | 
| +			return undef if (!defined($string)); | 
| +			# Check for end of record | 
| +			if ($string eq "") { | 
| +				return $filename; | 
| +			} | 
| +			$filename = $string; | 
| +			if (!exists($bb->{$function}->{$filename})) { | 
| +				$bb->{$function}->{$filename} = []; | 
| +			} | 
| +			next; | 
| +		} | 
| +		# Got an actual line number | 
| +		if (!defined($filename)) { | 
| +			warn("WARNING: unassigned line number in ". | 
| +			     "$bbg_filename\n"); | 
| +			next; | 
| +		} | 
| +		push(@{$bb->{$function}->{$filename}}, $lineno); | 
| +		graph_add_order($fileorder, $function, $filename); | 
| +	} | 
| +} | 
| + | 
| +# | 
| +# read_bbg(filename) | 
| +# | 
| +# 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, ... ] | 
| +# | 
| +# See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code for a description | 
| +# of the .bbg format. | 
| +# | 
| + | 
| +sub read_bbg($) | 
| +{ | 
| +	my ($bbg_filename) = @_; | 
| +	my $file_magic = 0x67626267; | 
| +	my $tag_function = 0x01000000; | 
| +	my $tag_lines = 0x01450000; | 
| +	my $word; | 
| +	my $tag; | 
| +	my $length; | 
| +	my $function; | 
| +	my $filename; | 
| +	my $bb = {}; | 
| +	my $fileorder = {}; | 
| +	my $instr; | 
| +	my $graph; | 
| +	local *HANDLE; | 
| + | 
| +	open(HANDLE, "<", $bbg_filename) or goto open_error; | 
| +	binmode(HANDLE); | 
| +	# Read magic | 
| +	$word = read_bbg_value(*HANDLE, "file magic"); | 
| +	goto incomplete if (!defined($word)); | 
| +	# Check magic | 
| +	if ($word != $file_magic) { | 
| +		goto magic_error; | 
| +	} | 
| +	# Skip version | 
| +	graph_skip(*HANDLE, 4, "version") or goto incomplete; | 
| +	while (!eof(HANDLE)) { | 
| +		# Read record tag | 
| +		$tag = read_bbg_value(*HANDLE, "record tag"); | 
| +		goto incomplete if (!defined($tag)); | 
| +		# Read record length | 
| +		$length = read_bbg_value(*HANDLE, "record length"); | 
| +		goto incomplete if (!defined($tag)); | 
| +		if ($tag == $tag_function) { | 
| +			graph_expect("function record"); | 
| +			# Read function name | 
| +			graph_expect("function name"); | 
| +			$function = read_bbg_string(*HANDLE); | 
| +			goto incomplete if (!defined($function)); | 
| +			$filename = undef; | 
| +			# Skip function checksum | 
| +			graph_skip(*HANDLE, 4, "function checksum") | 
| +				or goto incomplete; | 
| +		} elsif ($tag == $tag_lines) { | 
| +			# Read lines record | 
| +			$filename = read_bbg_lines_record(HANDLE, $bbg_filename, | 
| +					  $bb, $fileorder, $filename, | 
| +					  $function); | 
| +			goto incomplete if (!defined($filename)); | 
| +		} else { | 
| +			# Skip record contents | 
| +			graph_skip(*HANDLE, $length, "unhandled record") | 
| +				or goto incomplete; | 
| +		} | 
| +	} | 
| +	close(HANDLE); | 
| +	($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename); | 
| +	graph_cleanup($graph); | 
| + | 
| +	return ($instr, $graph); | 
| + | 
| +open_error: | 
| +	graph_error($bbg_filename, "could not open file"); | 
| +	return undef; | 
| +incomplete: | 
| +	graph_error($bbg_filename, "reached unexpected end of file"); | 
| +	return undef; | 
| +magic_error: | 
| +	graph_error($bbg_filename, "found unrecognized bbg file magic"); | 
| +	return undef; | 
| +} | 
| + | 
| +# | 
| +# read_gcno_word(handle[, description, peek]) | 
| +# | 
| +# Read and return a word in .gcno format. | 
| +# | 
| + | 
| +sub read_gcno_word(*;$$) | 
| +{ | 
| +	my ($handle, $desc, $peek) = @_; | 
| + | 
| +	return graph_read($handle, 4, $desc, $peek); | 
| +} | 
| + | 
| +# | 
| +# 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. If PEEK is non-zero, reset file | 
| +# position after read. | 
| +# | 
| + | 
| +sub read_gcno_value(*$;$$) | 
| +{ | 
| +	my ($handle, $big_endian, $desc, $peek) = @_; | 
| +	my $word; | 
| +	my $pos; | 
| + | 
| +	$word = read_gcno_word($handle, $desc, $peek); | 
| +	return undef if (!defined($word)); | 
| +	if ($big_endian) { | 
| +		return unpack("N", $word); | 
| +	} else { | 
| +		return unpack("V", $word); | 
| +	} | 
| +} | 
| + | 
| +# | 
| +# read_gcno_string(handle, big_endian) | 
| +# | 
| +# Read and return a string in .gcno format. | 
| +# | 
| + | 
| +sub read_gcno_string(*$) | 
| +{ | 
| +	my ($handle, $big_endian) = @_; | 
| +	my $length; | 
| +	my $string; | 
| + | 
| +	graph_expect("string"); | 
| +	# Read string length | 
| +	$length = read_gcno_value($handle, $big_endian, "string length"); | 
| +	return undef if (!defined($length)); | 
| +	if ($length == 0) { | 
| +		return ""; | 
| +	} | 
| +	$length *= 4; | 
| +	# Read string | 
| +	$string = graph_read($handle, $length, "string and padding"); | 
| +	return undef if (!defined($string)); | 
| +	$string =~ s/\0//g; | 
| + | 
| +	return $string; | 
| +} | 
| + | 
| +# | 
| +# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, | 
| +#                        function, big_endian) | 
| +# | 
| +# Read a gcno format lines record from handle and add the relevant data to | 
| +# bb and fileorder. Return filename on success, undef on error. | 
| +# | 
| + | 
| +sub read_gcno_lines_record(*$$$$$$) | 
| +{ | 
| +	my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, | 
| +	    $big_endian) = @_; | 
| +	my $string; | 
| +	my $lineno; | 
| + | 
| +	graph_expect("lines record"); | 
| +	# Skip basic block index | 
| +	graph_skip($handle, 4, "basic block index") or return undef; | 
| +	while (1) { | 
| +		# Read line number | 
| +		$lineno = read_gcno_value($handle, $big_endian, "line number"); | 
| +		return undef if (!defined($lineno)); | 
| +		if ($lineno == 0) { | 
| +			# Got a marker for a new filename | 
| +			graph_expect("filename"); | 
| +			$string = read_gcno_string($handle, $big_endian); | 
| +			return undef if (!defined($string)); | 
| +			# Check for end of record | 
| +			if ($string eq "") { | 
| +				return $filename; | 
| +			} | 
| +			$filename = $string; | 
| +			if (!exists($bb->{$function}->{$filename})) { | 
| +				$bb->{$function}->{$filename} = []; | 
| +			} | 
| +			next; | 
| +		} | 
| +		# Got an actual line number | 
| +		if (!defined($filename)) { | 
| +			warn("WARNING: unassigned line number in ". | 
| +			     "$gcno_filename\n"); | 
| +			next; | 
| +		} | 
| +		# Add to list | 
| +		push(@{$bb->{$function}->{$filename}}, $lineno); | 
| +		graph_add_order($fileorder, $function, $filename); | 
| +	} | 
| +} | 
| + | 
| +# | 
| +# 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. | 
| +# | 
| + | 
| +sub read_gcno_function_record(*$$$$) | 
| +{ | 
| +	my ($handle, $bb, $fileorder, $big_endian, $rec_length) = @_; | 
| +	my $filename; | 
| +	my $function; | 
| +	my $lineno; | 
| +	my $lines; | 
| + | 
| +	graph_expect("function record"); | 
| +	# Skip ident and checksum | 
| +	graph_skip($handle, 8, "function ident and checksum") or return undef; | 
| +	# 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); | 
| +	return undef if (!defined($function)); | 
| +	# Read filename | 
| +	graph_expect("filename"); | 
| +	$filename = read_gcno_string($handle, $big_endian); | 
| +	return undef if (!defined($filename)); | 
| +	# Read first line number | 
| +	$lineno = read_gcno_value($handle, $big_endian, "initial line number"); | 
| +	return undef if (!defined($lineno)); | 
| +	# Add to list | 
| +	push(@{$bb->{$function}->{$filename}}, $lineno); | 
| +	graph_add_order($fileorder, $function, $filename); | 
| + | 
| +	return ($filename, $function); | 
| +} | 
| + | 
| +# | 
| +# read_gcno(filename) | 
| +# | 
| +# Read the contents of the specified .gcno file and return the following | 
| +# mapping: | 
| +#   graph:    filename -> file_data | 
| +#   file_data: function name -> line_data | 
| +#   line_data: [ line1, line2, ... ] | 
| +# | 
| +# See the gcov-io.h file in the gcc 3.3 source code for a description of | 
| +# the .gcno format. | 
| +# | 
| + | 
| +sub read_gcno($) | 
| +{ | 
| +	my ($gcno_filename) = @_; | 
| +	my $file_magic = 0x67636e6f; | 
| +	my $tag_function = 0x01000000; | 
| +	my $tag_lines = 0x01450000; | 
| +	my $big_endian; | 
| +	my $word; | 
| +	my $tag; | 
| +	my $length; | 
| +	my $filename; | 
| +	my $function; | 
| +	my $bb = {}; | 
| +	my $fileorder = {}; | 
| +	my $instr; | 
| +	my $graph; | 
| +	local *HANDLE; | 
| + | 
| +	open(HANDLE, "<", $gcno_filename) or goto open_error; | 
| +	binmode(HANDLE); | 
| +	# Read magic | 
| +	$word = read_gcno_word(*HANDLE, "file magic"); | 
| +	goto incomplete if (!defined($word)); | 
| +	# Determine file endianness | 
| +	if (unpack("N", $word) == $file_magic) { | 
| +		$big_endian = 1; | 
| +	} elsif (unpack("V", $word) == $file_magic) { | 
| +		$big_endian = 0; | 
| +	} else { | 
| +		goto magic_error; | 
| +	} | 
| +	# Skip version and stamp | 
| +	graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete; | 
| +	while (!eof(HANDLE)) { | 
| +		my $next_pos; | 
| +		my $curr_pos; | 
| + | 
| +		# Read record tag | 
| +		$tag = read_gcno_value(*HANDLE, $big_endian, "record tag"); | 
| +		goto incomplete if (!defined($tag)); | 
| +		# Read record length | 
| +		$length = read_gcno_value(*HANDLE, $big_endian, | 
| +					  "record length"); | 
| +		goto incomplete if (!defined($length)); | 
| +		# Convert length to bytes | 
| +		$length *= 4; | 
| +		# Calculate start of next record | 
| +		$next_pos = tell(HANDLE); | 
| +		goto tell_error if ($next_pos == -1); | 
| +		$next_pos += $length; | 
| +		# Process record | 
| +		if ($tag == $tag_function) { | 
| +			($filename, $function) = read_gcno_function_record( | 
| +				*HANDLE, $bb, $fileorder, $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, | 
| +					$big_endian); | 
| +			goto incomplete if (!defined($filename)); | 
| +		} else { | 
| +			# Skip record contents | 
| +			graph_skip(*HANDLE, $length, "unhandled record") | 
| +				or goto incomplete; | 
| +		} | 
| +		# Ensure that we are at the start of the next record | 
| +		$curr_pos = tell(HANDLE); | 
| +		goto tell_error if ($curr_pos == -1); | 
| +		next if ($curr_pos == $next_pos); | 
| +		goto record_error if ($curr_pos > $next_pos); | 
| +		graph_skip(*HANDLE, $next_pos - $curr_pos, | 
| +			   "unhandled record content") | 
| +			or goto incomplete; | 
| +	} | 
| +	close(HANDLE); | 
| +	($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename); | 
| +	graph_cleanup($graph); | 
| + | 
| +	return ($instr, $graph); | 
| + | 
| +open_error: | 
| +	graph_error($gcno_filename, "could not open file"); | 
| +	return undef; | 
| +incomplete: | 
| +	graph_error($gcno_filename, "reached unexpected end of file"); | 
| +	return undef; | 
| +magic_error: | 
| +	graph_error($gcno_filename, "found unrecognized gcno file magic"); | 
| +	return undef; | 
| +tell_error: | 
| +	graph_error($gcno_filename, "could not determine file position"); | 
| +	return undef; | 
| +record_error: | 
| +	graph_error($gcno_filename, "found unrecognized record format"); | 
| +	return undef; | 
| +} | 
| + | 
| +sub debug($) | 
| +{ | 
| +	my ($msg) = @_; | 
| + | 
| +	return if (!$debug); | 
| +	print(STDERR "DEBUG: $msg"); | 
| +} | 
| + | 
| +# | 
| +# get_gcov_capabilities | 
| +# | 
| +# Determine the list of available gcov options. | 
| +# | 
| + | 
| +sub get_gcov_capabilities() | 
| +{ | 
| +	my $help = `$gcov_tool --help`; | 
| +	my %capabilities; | 
| + | 
| +	foreach (split(/\n/, $help)) { | 
| +		next if (!/--(\S+)/); | 
| +		next if ($1 eq 'help'); | 
| +		next if ($1 eq 'version'); | 
| +		next if ($1 eq 'object-directory'); | 
| + | 
| +		$capabilities{$1} = 1; | 
| +		debug("gcov has capability '$1'\n"); | 
| +	} | 
| + | 
| +	return \%capabilities; | 
| +} | 
| + | 
| +# | 
| +# 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; | 
| +} | 
|  |