| Index: third_party/lcov/bin/genhtml | 
| diff --git a/third_party/lcov/bin/genhtml b/third_party/lcov/bin/genhtml | 
| index af573ff7ed05a3cca0eb5edd606d47214bc48f91..8979f8c813d4b8418cfae6ff28a561d6fe6cab17 100755 | 
| --- a/third_party/lcov/bin/genhtml | 
| +++ b/third_party/lcov/bin/genhtml | 
| @@ -1,6 +1,6 @@ | 
| #!/usr/bin/perl -w | 
| # | 
| -#   Copyright (c) International Business Machines  Corp., 2002 | 
| +#   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 | 
| @@ -72,7 +72,7 @@ use Digest::MD5 qw(md5_base64); | 
|  | 
| # Global constants | 
| our $title		= "LCOV - code coverage report"; | 
| -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 $tool_name		= basename($0); | 
|  | 
| @@ -81,13 +81,17 @@ our $tool_name		= basename($0); | 
| # MED: $med_limit <= rate <  $hi_limit    graph color: orange | 
| # LO:          0  <= rate <  $med_limit   graph color: red | 
|  | 
| -# For line coverage | 
| -our $hi_limit	= 50; | 
| -our $med_limit	= 15; | 
| +# For line coverage/all coverage types if not specified | 
| +our $hi_limit = 90; | 
| +our $med_limit = 75; | 
|  | 
| # For function coverage | 
| -our $fn_hi_limit	= 90; | 
| -our $fn_med_limit	= 75; | 
| +our $fn_hi_limit; | 
| +our $fn_med_limit; | 
| + | 
| +# For branch coverage | 
| +our $br_hi_limit; | 
| +our $br_med_limit; | 
|  | 
| # Width of overview image | 
| our $overview_width = 80; | 
| @@ -107,7 +111,55 @@ our $nav_offset = 10; | 
| # specifies that offset in lines. | 
| our $func_offset = 2; | 
|  | 
| -our $overview_title = "directory"; | 
| +our $overview_title = "top level"; | 
| + | 
| +# Width for line coverage information in the source code view | 
| +our $line_field_width = 12; | 
| + | 
| +# Width for branch coverage information in the source code view | 
| +our $br_field_width = 16; | 
| + | 
| +# Internal Constants | 
| + | 
| +# Header types | 
| +our $HDR_DIR		= 0; | 
| +our $HDR_FILE		= 1; | 
| +our $HDR_SOURCE		= 2; | 
| +our $HDR_TESTDESC	= 3; | 
| +our $HDR_FUNC		= 4; | 
| + | 
| +# Sort types | 
| +our $SORT_FILE		= 0; | 
| +our $SORT_LINE		= 1; | 
| +our $SORT_FUNC		= 2; | 
| +our $SORT_BRANCH	= 3; | 
| + | 
| +# Fileview heading types | 
| +our $HEAD_NO_DETAIL	= 1; | 
| +our $HEAD_DETAIL_HIDDEN	= 2; | 
| +our $HEAD_DETAIL_SHOWN	= 3; | 
| + | 
| +# Offsets for storing branch coverage data in vectors | 
| +our $BR_BLOCK		= 0; | 
| +our $BR_BRANCH		= 1; | 
| +our $BR_TAKEN		= 2; | 
| +our $BR_VEC_ENTRIES	= 3; | 
| +our $BR_VEC_WIDTH	= 32; | 
| + | 
| +# Additional offsets used when converting branch coverage data to HTML | 
| +our $BR_LEN	= 3; | 
| +our $BR_OPEN	= 4; | 
| +our $BR_CLOSE	= 5; | 
| + | 
| +# Branch data combination types | 
| +our $BR_SUB = 0; | 
| +our $BR_ADD = 1; | 
| + | 
| +# Error classes which users may specify to ignore during processing | 
| +our $ERROR_SOURCE	= 0; | 
| +our %ERROR_ID = ( | 
| +	"source" => $ERROR_SOURCE, | 
| +); | 
|  | 
| # Data related prototypes | 
| sub print_usage(*); | 
| @@ -118,21 +170,20 @@ sub process_file($$$); | 
| sub info(@); | 
| sub read_info_file($); | 
| sub get_info_entry($); | 
| -sub set_info_entry($$$$$$$;$$$$); | 
| -sub get_prefix(@); | 
| +sub set_info_entry($$$$$$$$$;$$$$$$); | 
| +sub get_prefix($@); | 
| sub shorten_prefix($); | 
| sub get_dir_list(@); | 
| sub get_relative_base_path($); | 
| sub read_testfile($); | 
| sub get_date_string(); | 
| -sub split_filename($); | 
| sub create_sub_dir($); | 
| sub subtract_counts($$); | 
| sub add_counts($$); | 
| sub apply_baseline($$); | 
| sub remove_unused_descriptions(); | 
| sub get_found_and_hit($); | 
| -sub get_affecting_tests($$); | 
| +sub get_affecting_tests($$$); | 
| sub combine_info_files($$); | 
| sub merge_checksums($$$); | 
| sub combine_info_entries($$$); | 
| @@ -142,6 +193,19 @@ sub read_config($); | 
| sub apply_config($); | 
| sub get_html_prolog($); | 
| sub get_html_epilog($); | 
| +sub write_dir_page($$$$$$$$$$$$$$$$$); | 
| +sub classify_rate($$$$); | 
| +sub br_taken_add($$); | 
| +sub br_taken_sub($$); | 
| +sub br_ivec_len($); | 
| +sub br_ivec_get($$); | 
| +sub br_ivec_push($$$$); | 
| +sub combine_brcount($$$); | 
| +sub get_br_found_and_hit($); | 
| +sub warn_handler($); | 
| +sub die_handler($); | 
| +sub parse_ignore_errors(@); | 
| +sub rate($$;$$$); | 
|  | 
|  | 
| # HTML related prototypes | 
| @@ -151,32 +215,31 @@ sub get_bar_graph_code($$$); | 
| sub write_png_files(); | 
| sub write_htaccess_file(); | 
| sub write_css_file(); | 
| -sub write_description_file($$$$$); | 
| -sub write_function_rable(*$$$); | 
| +sub write_description_file($$$$$$$); | 
| +sub write_function_table(*$$$$$$$$$$); | 
|  | 
| sub write_html(*$); | 
| sub write_html_prolog(*$$); | 
| sub write_html_epilog(*$;$); | 
|  | 
| -sub write_header(*$$$$$$$$); | 
| +sub write_header(*$$$$$$$$$$); | 
| sub write_header_prolog(*$); | 
| -sub write_header_line(*$@); | 
| +sub write_header_line(*@); | 
| sub write_header_epilog(*$); | 
|  | 
| -sub write_file_table(*$$$$$$); | 
| -sub write_file_table_prolog(*$$$); | 
| -sub write_file_table_entry(*$$$$$$$); | 
| -sub write_file_table_detail_heading(*$$$); | 
| -sub write_file_table_detail_entry(*$$$$$); | 
| +sub write_file_table(*$$$$$$$); | 
| +sub write_file_table_prolog(*$@); | 
| +sub write_file_table_entry(*$$$@); | 
| +sub write_file_table_detail_entry(*$@); | 
| sub write_file_table_epilog(*); | 
|  | 
| sub write_test_table_prolog(*$); | 
| sub write_test_table_entry(*$$); | 
| sub write_test_table_epilog(*); | 
|  | 
| -sub write_source($$$$$$); | 
| +sub write_source($$$$$$$); | 
| sub write_source_prolog(*); | 
| -sub write_source_line(*$$$$$); | 
| +sub write_source_line(*$$$$$$); | 
| sub write_source_epilog(*); | 
|  | 
| sub write_frameset(*$$$); | 
| @@ -204,8 +267,10 @@ our $help;		# Help option flag | 
| our $version;		# Version option flag | 
| our $show_details;	# If set, generate detailed directory view | 
| our $no_prefix;		# If set, do not remove filename prefix | 
| -our $func_coverage = 1;	# If set, generate function coverage statistics | 
| +our $func_coverage;	# If set, generate function coverage statistics | 
| our $no_func_coverage;	# Disable func_coverage | 
| +our $br_coverage;	# If set, generate branch coverage statistics | 
| +our $no_br_coverage;	# Disable br_coverage | 
| our $sort = 1;		# If set, provide directory listings with sorted entries | 
| our $no_sort;		# Disable sort | 
| our $frames;		# If set, use frames for source code view | 
| @@ -221,11 +286,19 @@ our $html_prolog;	# Actual HTML prolog | 
| our $html_epilog;	# Actual HTML epilog | 
| our $html_ext = "html";	# Extension for generated HTML files | 
| our $html_gzip = 0;	# Compress with gzip | 
| +our $demangle_cpp = 0;	# Demangle C++ function names | 
| +our @opt_ignore_errors;	# Ignore certain error classes during processing | 
| +our @ignore; | 
| +our $opt_config_file;	# User-specified configuration file location | 
| +our %opt_rc; | 
| +our $charset = "UTF-8";	# Default charset for HTML pages | 
| our @fileview_sortlist; | 
| -our @fileview_sortname = ("", "-sort-l", "-sort-f"); | 
| +our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b"); | 
| our @funcview_sortlist; | 
| our @rate_name = ("Lo", "Med", "Hi"); | 
| our @rate_png = ("ruby.png", "amber.png", "emerald.png"); | 
| +our $lcov_func_coverage = 1; | 
| +our $lcov_branch_coverage = 0; | 
|  | 
| our $cwd = `pwd`;	# Current working directory | 
| chomp($cwd); | 
| @@ -239,14 +312,25 @@ our $tool_dir = dirname($0);	# Directory where genhtml tool is installed | 
| $SIG{__WARN__} = \&warn_handler; | 
| $SIG{__DIE__} = \&die_handler; | 
|  | 
| +# Prettify version string | 
| +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; | 
| + | 
| # Add current working directory if $tool_dir is not already an absolute path | 
| if (! ($tool_dir =~ /^\/(.*)$/)) | 
| { | 
| $tool_dir = "$cwd/$tool_dir"; | 
| } | 
|  | 
| +# 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"); | 
| } | 
| @@ -255,13 +339,14 @@ elsif (-r "/etc/lcovrc") | 
| $config = read_config("/etc/lcovrc"); | 
| } | 
|  | 
| -if ($config) | 
| +if ($config || %opt_rc) | 
| { | 
| -	# Copy configuration file values to variables | 
| +	# Copy configuration file and --rc values to variables | 
| apply_config({ | 
| "genhtml_css_file"		=> \$css_filename, | 
| "genhtml_hi_limit"		=> \$hi_limit, | 
| "genhtml_med_limit"		=> \$med_limit, | 
| +		"genhtml_line_field_width"	=> \$line_field_width, | 
| "genhtml_overview_width"	=> \$overview_width, | 
| "genhtml_nav_resolution"	=> \$nav_resolution, | 
| "genhtml_nav_offset"		=> \$nav_offset, | 
| @@ -278,36 +363,57 @@ if ($config) | 
| "genhtml_function_hi_limit"	=> \$fn_hi_limit, | 
| "genhtml_function_med_limit"	=> \$fn_med_limit, | 
| "genhtml_function_coverage"	=> \$func_coverage, | 
| +		"genhtml_branch_hi_limit"	=> \$br_hi_limit, | 
| +		"genhtml_branch_med_limit"	=> \$br_med_limit, | 
| +		"genhtml_branch_coverage"	=> \$br_coverage, | 
| +		"genhtml_branch_field_width"	=> \$br_field_width, | 
| "genhtml_sort"			=> \$sort, | 
| +		"genhtml_charset"		=> \$charset, | 
| +		"lcov_function_coverage"	=> \$lcov_func_coverage, | 
| +		"lcov_branch_coverage"		=> \$lcov_branch_coverage, | 
| }); | 
| } | 
|  | 
| +# Copy related values if not specified | 
| +$fn_hi_limit	= $hi_limit if (!defined($fn_hi_limit)); | 
| +$fn_med_limit	= $med_limit if (!defined($fn_med_limit)); | 
| +$br_hi_limit	= $hi_limit if (!defined($br_hi_limit)); | 
| +$br_med_limit	= $med_limit if (!defined($br_med_limit)); | 
| +$func_coverage	= $lcov_func_coverage if (!defined($func_coverage)); | 
| +$br_coverage	= $lcov_branch_coverage if (!defined($br_coverage)); | 
| + | 
| # Parse command line options | 
| -if (!GetOptions("output-directory=s"	=> \$output_directory, | 
| -		"title=s"		=> \$test_title, | 
| -		"description-file=s"	=> \$desc_filename, | 
| -		"keep-descriptions"	=> \$keep_descriptions, | 
| -		"css-file=s"		=> \$css_filename, | 
| -		"baseline-file=s"	=> \$base_filename, | 
| -		"prefix=s"		=> \$dir_prefix, | 
| +if (!GetOptions("output-directory|o=s"	=> \$output_directory, | 
| +		"title|t=s"		=> \$test_title, | 
| +		"description-file|d=s"	=> \$desc_filename, | 
| +		"keep-descriptions|k"	=> \$keep_descriptions, | 
| +		"css-file|c=s"		=> \$css_filename, | 
| +		"baseline-file|b=s"	=> \$base_filename, | 
| +		"prefix|p=s"		=> \$dir_prefix, | 
| "num-spaces=i"		=> \$tab_size, | 
| "no-prefix"		=> \$no_prefix, | 
| "no-sourceview"		=> \$no_sourceview, | 
| -		"show-details"		=> \$show_details, | 
| -		"frames"		=> \$frames, | 
| +		"show-details|s"	=> \$show_details, | 
| +		"frames|f"		=> \$frames, | 
| "highlight"		=> \$highlight, | 
| "legend"		=> \$legend, | 
| -		"quiet"			=> \$quiet, | 
| +		"quiet|q"		=> \$quiet, | 
| "help|h|?"		=> \$help, | 
| -		"version"		=> \$version, | 
| +		"version|v"		=> \$version, | 
| "html-prolog=s"		=> \$html_prolog_file, | 
| "html-epilog=s"		=> \$html_epilog_file, | 
| "html-extension=s"	=> \$html_ext, | 
| "html-gzip"		=> \$html_gzip, | 
| "function-coverage"	=> \$func_coverage, | 
| "no-function-coverage"	=> \$no_func_coverage, | 
| +		"branch-coverage"	=> \$br_coverage, | 
| +		"no-branch-coverage"	=> \$no_br_coverage, | 
| "sort"			=> \$sort, | 
| "no-sort"		=> \$no_sort, | 
| +		"demangle-cpp"		=> \$demangle_cpp, | 
| +		"ignore-errors=s"	=> \@opt_ignore_errors, | 
| +		"config-file=s"		=> \$opt_config_file, | 
| +		"rc=s%"			=> \%opt_rc, | 
| )) | 
| { | 
| print(STDERR "Use $tool_name --help to get usage information\n"); | 
| @@ -317,6 +423,9 @@ if (!GetOptions("output-directory=s"	=> \$output_directory, | 
| if ($no_func_coverage) { | 
| $func_coverage = 0; | 
| } | 
| +	if ($no_br_coverage) { | 
| +		$br_coverage = 0; | 
| +	} | 
|  | 
| # Merge sort options | 
| if ($no_sort) { | 
| @@ -340,6 +449,9 @@ if ($version) | 
| exit(0); | 
| } | 
|  | 
| +# Determine which errors the user wants us to ignore | 
| +parse_ignore_errors(@opt_ignore_errors); | 
| + | 
| # Check for info filename | 
| if (!@info_filenames) | 
| { | 
| @@ -400,16 +512,14 @@ if ($no_prefix && defined($dir_prefix)) | 
| $dir_prefix = undef; | 
| } | 
|  | 
| +@fileview_sortlist = ($SORT_FILE); | 
| +@funcview_sortlist = ($SORT_FILE); | 
| + | 
| if ($sort) { | 
| -	@funcview_sortlist = (0, 1); | 
| -	if ($func_coverage) { | 
| -		@fileview_sortlist = (0, 1, 2); | 
| -	} else { | 
| -		@fileview_sortlist = (0, 1); | 
| -	} | 
| -} else { | 
| -	@fileview_sortlist = (0); | 
| -	@funcview_sortlist = (0); | 
| +	push(@fileview_sortlist, $SORT_LINE); | 
| +	push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage); | 
| +	push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage); | 
| +	push(@funcview_sortlist, $SORT_LINE); | 
| } | 
|  | 
| if ($frames) | 
| @@ -418,6 +528,15 @@ if ($frames) | 
| do("$tool_dir/genpng"); | 
| } | 
|  | 
| +# Ensure that the c++filt tool is available when using --demangle-cpp | 
| +if ($demangle_cpp) | 
| +{ | 
| +	if (system_no_output(3, "c++filt", "--version")) { | 
| +		die("ERROR: could not find c++filt tool needed for ". | 
| +		    "--demangle-cpp\n"); | 
| +	} | 
| +} | 
| + | 
| # Make sure output_directory exists, create it if necessary | 
| if ($output_directory) | 
| { | 
| @@ -425,8 +544,7 @@ if ($output_directory) | 
|  | 
| if (! -e _) | 
| { | 
| -		system("mkdir", "-p", $output_directory) | 
| -			and die("ERROR: cannot create directory $_!\n"); | 
| +		create_sub_dir($output_directory); | 
| } | 
| } | 
|  | 
| @@ -457,6 +575,9 @@ Misc: | 
| -h, --help                        Print this help, then exit | 
| -v, --version                     Print version number, then exit | 
| -q, --quiet                       Do not print progress messages | 
| +      --config-file FILENAME        Specify configuration file location | 
| +      --rc SETTING=VALUE            Override configuration file setting | 
| +      --ignore-errors ERRORS        Continue after ERRORS (source) | 
|  | 
| Operation: | 
| -o, --output-directory OUTDIR     Write HTML output to OUTDIR | 
| @@ -467,6 +588,7 @@ Operation: | 
| -p, --prefix PREFIX               Remove PREFIX from all directory names | 
| --no-prefix                   Do not remove prefix from directory names | 
| --(no-)function-coverage      Enable (disable) function coverage display | 
| +      --(no-)branch-coverage        Enable (disable) branch coverage display | 
|  | 
| HTML output: | 
| -f, --frames                      Use HTML frames for source code view | 
| @@ -481,6 +603,7 @@ HTML output: | 
| --html-extension EXT          Use EXT as filename extension for pages | 
| --html-gzip                   Use gzip to compress HTML | 
| --(no-)sort                   Enable (disable) sorted coverage views | 
| +      --demangle-cpp                Demangle C++ function names | 
|  | 
| For more information see: $lcov_url | 
| END_OF_USAGE | 
| @@ -508,6 +631,49 @@ sub get_rate($$) | 
|  | 
|  | 
| # | 
| +# get_overall_line(found, hit, name_singular, name_plural) | 
| +# | 
| +# Return a string containing overall information for the specified | 
| +# found/hit data. | 
| +# | 
| + | 
| +sub get_overall_line($$$$) | 
| +{ | 
| +	my ($found, $hit, $name_sn, $name_pl) = @_; | 
| +	my $name; | 
| + | 
| +	return "no data found" if (!defined($found) || $found == 0); | 
| +	$name = ($found == 1) ? $name_sn : $name_pl; | 
| +	return rate($hit, $found, "% ($hit of $found $name)"); | 
| +} | 
| + | 
| + | 
| +# | 
| +# print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do | 
| +#                    br_found, br_hit) | 
| +# | 
| +# Print overall coverage rates for the specified coverage types. | 
| +# | 
| + | 
| +sub print_overall_rate($$$$$$$$$) | 
| +{ | 
| +	my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit, | 
| +	    $br_do, $br_found, $br_hit) = @_; | 
| + | 
| +	info("Overall coverage rate:\n"); | 
| +	info("  lines......: %s\n", | 
| +	     get_overall_line($ln_found, $ln_hit, "line", "lines")) | 
| +		if ($ln_do); | 
| +	info("  functions..: %s\n", | 
| +	     get_overall_line($fn_found, $fn_hit, "function", "functions")) | 
| +		if ($fn_do); | 
| +	info("  branches...: %s\n", | 
| +	     get_overall_line($br_found, $br_hit, "branch", "branches")) | 
| +		if ($br_do); | 
| +} | 
| + | 
| + | 
| +# | 
| # gen_html() | 
| # | 
| # Generate a set of HTML pages from contents of .info file INFO_FILENAME. | 
| @@ -527,10 +693,14 @@ sub gen_html() | 
| my $lines_hit; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
| my $overall_found = 0; | 
| my $overall_hit = 0; | 
| my $total_fn_found = 0; | 
| my $total_fn_hit = 0; | 
| +	my $total_br_found = 0; | 
| +	my $total_br_hit = 0; | 
| my $dir_name; | 
| my $link_name; | 
| my @dir_list; | 
| @@ -570,7 +740,7 @@ sub gen_html() | 
| elsif (!defined($dir_prefix)) | 
| { | 
| # Get prefix common to most directories in list | 
| -		$dir_prefix = get_prefix(@dir_list); | 
| +		$dir_prefix = get_prefix(1, keys(%info_data)); | 
|  | 
| if ($dir_prefix) | 
| { | 
| @@ -625,9 +795,13 @@ sub gen_html() | 
| # Process each subdirectory and collect overview information | 
| foreach $dir_name (@dir_list) | 
| { | 
| -		($lines_found, $lines_hit, $fn_found, $fn_hit) | 
| +		($lines_found, $lines_hit, $fn_found, $fn_hit, | 
| +		 $br_found, $br_hit) | 
| = process_dir($dir_name); | 
|  | 
| +		# Handle files in root directory gracefully | 
| +		$dir_name = "root" if ($dir_name eq ""); | 
| + | 
| # Remove prefix if applicable | 
| if (!$no_prefix && $dir_prefix) | 
| { | 
| @@ -646,13 +820,16 @@ sub gen_html() | 
| } | 
|  | 
| $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found, | 
| -					$fn_hit, $link_name, | 
| +					$fn_hit, $br_found, $br_hit, $link_name, | 
| get_rate($lines_found, $lines_hit), | 
| -					get_rate($fn_found, $fn_hit)]; | 
| +					get_rate($fn_found, $fn_hit), | 
| +					get_rate($br_found, $br_hit)]; | 
| $overall_found	+= $lines_found; | 
| $overall_hit	+= $lines_hit; | 
| $total_fn_found	+= $fn_found; | 
| $total_fn_hit	+= $fn_hit; | 
| +		$total_br_found	+= $br_found; | 
| +		$total_br_hit	+= $br_hit; | 
| } | 
|  | 
| # Generate overview page | 
| @@ -662,8 +839,8 @@ sub gen_html() | 
| foreach (@fileview_sortlist) { | 
| write_dir_page($fileview_sortname[$_], ".", "", $test_title, | 
| undef, $overall_found, $overall_hit, | 
| -			       $total_fn_found, $total_fn_hit, \%overview, | 
| -			       {}, {}, 0, $_); | 
| +			       $total_fn_found, $total_fn_hit, $total_br_found, | 
| +			       $total_br_hit, \%overview, {}, {}, {}, 0, $_); | 
| } | 
|  | 
| # Check if there are any test case descriptions to write out | 
| @@ -672,37 +849,15 @@ sub gen_html() | 
| info("Writing test case description file.\n"); | 
| write_description_file( \%test_description, | 
| $overall_found, $overall_hit, | 
| -					$total_fn_found, $total_fn_hit); | 
| -	} | 
| - | 
| -	chdir($cwd); | 
| - | 
| -	info("Overall coverage rate:\n"); | 
| - | 
| -	if ($overall_found == 0) | 
| -	{ | 
| -		info("  lines......: no data found\n"); | 
| -		return; | 
| +					$total_fn_found, $total_fn_hit, | 
| +					$total_br_found, $total_br_hit); | 
| } | 
| -	info("  lines......: %.1f%% (%d of %d lines)\n", | 
| -	     $overall_hit * 100 / $overall_found, $overall_hit, | 
| -	     $overall_found,); | 
| - | 
| -	if ($func_coverage) | 
| -	{ | 
| -		if ($total_fn_found == 0) | 
| -		{ | 
| -			info("  functions..: no data found\n"); | 
| -		} | 
| -		else | 
| -		{ | 
| -			info("  functions..: %.1f%% (%d of %d functions)\n", | 
| -			     $total_fn_hit * 100 / $total_fn_found, | 
| -			     $total_fn_hit, $total_fn_found); | 
|  | 
| -		} | 
| -	} | 
| +	print_overall_rate(1, $overall_found, $overall_hit, | 
| +			   $func_coverage, $total_fn_found, $total_fn_hit, | 
| +			   $br_coverage, $total_br_found, $total_br_hit); | 
|  | 
| +	chdir($cwd); | 
| } | 
|  | 
| # | 
| @@ -716,34 +871,36 @@ sub html_create($$) | 
|  | 
| if ($html_gzip) | 
| { | 
| -		open($handle, "|gzip -c >$filename") | 
| +		open($handle, "|-", "gzip -c >'$filename'") | 
| or die("ERROR: cannot open $filename for writing ". | 
| "(gzip)!\n"); | 
| } | 
| else | 
| { | 
| -		open($handle, ">$filename") | 
| +		open($handle, ">", $filename) | 
| or die("ERROR: cannot open $filename for writing!\n"); | 
| } | 
| } | 
|  | 
| -sub write_dir_page($$$$$$$$$$$$$$) | 
| +sub write_dir_page($$$$$$$$$$$$$$$$$) | 
| { | 
| my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found, | 
| -	    $overall_hit, $total_fn_found, $total_fn_hit, $overview, | 
| -	    $testhash, $testfnchash, $view_type, $sort_type) = @_; | 
| +	    $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found, | 
| +	    $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash, | 
| +	    $view_type, $sort_type) = @_; | 
|  | 
| # Generate directory overview page including details | 
| html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext"); | 
| if (!defined($trunc_dir)) { | 
| $trunc_dir = ""; | 
| } | 
| +	$title .= " - " if ($trunc_dir ne ""); | 
| write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir"); | 
| write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir, | 
| $overall_found, $overall_hit, $total_fn_found, | 
| -		     $total_fn_hit, $sort_type); | 
| +		     $total_fn_hit, $total_br_found, $total_br_hit, $sort_type); | 
| write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash, | 
| -			 $testfnchash, $view_type, $sort_type); | 
| +			 $testfnchash, $testbrhash, $view_type, $sort_type); | 
| write_html_epilog(*HTML_HANDLE, $base_dir); | 
| close(*HTML_HANDLE); | 
| } | 
| @@ -765,16 +922,22 @@ sub process_dir($) | 
| my $lines_hit; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
| my $overall_found=0; | 
| my $overall_hit=0; | 
| my $total_fn_found=0; | 
| my $total_fn_hit=0; | 
| +	my $total_br_found = 0; | 
| +	my $total_br_hit = 0; | 
| my $base_name; | 
| my $extension; | 
| my $testdata; | 
| my %testhash; | 
| my $testfncdata; | 
| my %testfnchash; | 
| +	my $testbrdata; | 
| +	my %testbrhash; | 
| my @sort_list; | 
| local *HTML_HANDLE; | 
|  | 
| @@ -793,6 +956,10 @@ sub process_dir($) | 
| $rel_dir = substr($rel_dir, 1); | 
| } | 
|  | 
| +	# Handle files in root directory gracefully | 
| +	$rel_dir = "root" if ($rel_dir eq ""); | 
| +	$trunc_dir = "root" if ($trunc_dir eq ""); | 
| + | 
| $base_dir = get_relative_base_path($rel_dir); | 
|  | 
| create_sub_dir($rel_dir); | 
| @@ -804,8 +971,9 @@ sub process_dir($) | 
| my $page_link; | 
| my $func_link; | 
|  | 
| -		($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata, | 
| -		 $testfncdata) = process_file($trunc_dir, $rel_dir, $filename); | 
| +		($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, | 
| +		 $br_hit, $testdata, $testfncdata, $testbrdata) = | 
| +			process_file($trunc_dir, $rel_dir, $filename); | 
|  | 
| $base_name = basename($filename); | 
|  | 
| @@ -819,18 +987,24 @@ sub process_dir($) | 
| $page_link = "$base_name.gcov.$html_ext"; | 
| } | 
| $overview{$base_name} = [$lines_found, $lines_hit, $fn_found, | 
| -					 $fn_hit, $page_link, | 
| +					 $fn_hit, $br_found, $br_hit, | 
| +					 $page_link, | 
| get_rate($lines_found, $lines_hit), | 
| -					 get_rate($fn_found, $fn_hit)]; | 
| +					 get_rate($fn_found, $fn_hit), | 
| +					 get_rate($br_found, $br_hit)]; | 
|  | 
| $testhash{$base_name} = $testdata; | 
| $testfnchash{$base_name} = $testfncdata; | 
| +		$testbrhash{$base_name} = $testbrdata; | 
|  | 
| $overall_found	+= $lines_found; | 
| $overall_hit	+= $lines_hit; | 
|  | 
| $total_fn_found += $fn_found; | 
| $total_fn_hit   += $fn_hit; | 
| + | 
| +		$total_br_found += $br_found; | 
| +		$total_br_hit   += $br_hit; | 
| } | 
|  | 
| # Create sorted pages | 
| @@ -839,7 +1013,8 @@ sub process_dir($) | 
| write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir, | 
| $test_title, $trunc_dir, $overall_found, | 
| $overall_hit, $total_fn_found, $total_fn_hit, | 
| -			       \%overview, {}, {}, 1, $_); | 
| +			       $total_br_found, $total_br_hit, \%overview, {}, | 
| +			       {}, {}, 1, $_); | 
| if (!$show_details) { | 
| next; | 
| } | 
| @@ -847,12 +1022,14 @@ sub process_dir($) | 
| write_dir_page("-detail".$fileview_sortname[$_], $rel_dir, | 
| $base_dir, $test_title, $trunc_dir, | 
| $overall_found, $overall_hit, $total_fn_found, | 
| -			       $total_fn_hit, \%overview, \%testhash, | 
| -			       \%testfnchash, 1, $_); | 
| +			       $total_fn_hit, $total_br_found, $total_br_hit, | 
| +			       \%overview, \%testhash, \%testfnchash, | 
| +			       \%testbrhash, 1, $_); | 
| } | 
|  | 
| # Calculate resulting line counts | 
| -	return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit); | 
| +	return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit, | 
| +		$total_br_found, $total_br_hit); | 
| } | 
|  | 
|  | 
| @@ -913,11 +1090,12 @@ sub get_converted_lines($) | 
| } | 
|  | 
|  | 
| -sub write_function_page($$$$$$$$$$$$$$) | 
| +sub write_function_page($$$$$$$$$$$$$$$$$$) | 
| { | 
| my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title, | 
| -	    $lines_found, $lines_hit, $fn_found, $fn_hit, | 
| -	    $sumcount, $funcdata, $sumfnccount, $testfncdata, $sort_type) = @_; | 
| +	    $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit, | 
| +	    $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount, | 
| +	    $testbrdata, $sort_type) = @_; | 
| my $pagetitle; | 
| my $filename; | 
|  | 
| @@ -932,10 +1110,11 @@ sub write_function_page($$$$$$$$$$$$$$) | 
| write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); | 
| write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name", | 
| "$rel_dir/$base_name", $lines_found, $lines_hit, | 
| -		     $fn_found, $fn_hit, $sort_type); | 
| +		     $fn_found, $fn_hit, $br_found, $br_hit, $sort_type); | 
| write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext", | 
| $sumcount, $funcdata, | 
| -			     $sumfnccount, $testfncdata, $base_name, | 
| +			     $sumfnccount, $testfncdata, $sumbrcount, | 
| +			     $testbrdata, $base_name, | 
| $base_dir, $sort_type); | 
| write_html_epilog(*HTML_HANDLE, $base_dir, 1); | 
| close(*HTML_HANDLE); | 
| @@ -962,25 +1141,31 @@ sub process_file($$$) | 
| my $checkdata; | 
| my $testfncdata; | 
| my $sumfnccount; | 
| +	my $testbrdata; | 
| +	my $sumbrcount; | 
| my $lines_found; | 
| my $lines_hit; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
| my $converted; | 
| my @source; | 
| my $pagetitle; | 
| local *HTML_HANDLE; | 
|  | 
| ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, | 
| -	 $sumfnccount, $lines_found, $lines_hit, $fn_found, $fn_hit) | 
| +	 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit, | 
| +	 $fn_found, $fn_hit, $br_found, $br_hit) | 
| = get_info_entry($info_data{$filename}); | 
|  | 
| # Return after this point in case user asked us not to generate | 
| # source code view | 
| if ($no_sourceview) | 
| { | 
| -		return ($lines_found, $lines_hit, | 
| -			$fn_found, $fn_hit, $testdata); | 
| +		return ($lines_found, $lines_hit, $fn_found, $fn_hit, | 
| +			$br_found, $br_hit, $testdata, $testfncdata, | 
| +			$testbrdata); | 
| } | 
|  | 
| $converted = get_converted_lines($testdata); | 
| @@ -990,9 +1175,9 @@ sub process_file($$$) | 
| write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); | 
| write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name", | 
| "$rel_dir/$base_name", $lines_found, $lines_hit, | 
| -		     $fn_found, $fn_hit, 0); | 
| +		     $fn_found, $fn_hit, $br_found, $br_hit, 0); | 
| @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata, | 
| -			       $converted, $funcdata); | 
| +			       $converted, $funcdata, $sumbrcount); | 
|  | 
| write_html_epilog(*HTML_HANDLE, $base_dir, 1); | 
| close(*HTML_HANDLE); | 
| @@ -1003,17 +1188,20 @@ sub process_file($$$) | 
| write_function_page($base_dir, $rel_dir, $trunc_dir, | 
| $base_name, $test_title, | 
| $lines_found, $lines_hit, | 
| -					    $fn_found, $fn_hit, $sumcount, | 
| +					    $fn_found, $fn_hit, $br_found, | 
| +					    $br_hit, $sumcount, | 
| $funcdata, $sumfnccount, | 
| -					    $testfncdata, $_); | 
| +					    $testfncdata, $sumbrcount, | 
| +					    $testbrdata, $_); | 
| } | 
| } | 
|  | 
| # Additional files are needed in case of frame output | 
| if (!$frames) | 
| { | 
| -		return ($lines_found, $lines_hit, | 
| -			$fn_found, $fn_hit, $testdata); | 
| +		return ($lines_found, $lines_hit, $fn_found, $fn_hit, | 
| +			$br_found, $br_hit, $testdata, $testfncdata, | 
| +			$testbrdata); | 
| } | 
|  | 
| # Create overview png file | 
| @@ -1033,8 +1221,8 @@ sub process_file($$$) | 
| scalar(@source)); | 
| close(*HTML_HANDLE); | 
|  | 
| -	return ($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata, | 
| -		$testfncdata); | 
| +	return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, | 
| +		$br_hit, $testdata, $testfncdata, $testbrdata); | 
| } | 
|  | 
|  | 
| @@ -1054,16 +1242,22 @@ sub process_file($$$) | 
| #        "check" -> \%checkdata | 
| #        "testfnc" -> \%testfncdata | 
| #        "sumfnc"  -> \%sumfnccount | 
| +#        "testbr"  -> \%testbrdata | 
| +#        "sumbr"   -> \%sumbrcount | 
| # | 
| # %testdata   : name of test affecting this file -> \%testcount | 
| # %testfncdata: name of test affecting this file -> \%testfnccount | 
| +# %testbrdata:  name of test affecting this file -> \%testbrcount | 
| # | 
| # %testcount   : line number   -> execution count for a single test | 
| # %testfnccount: function name -> execution count for a single test | 
| +# %testbrcount : line number   -> branch coverage data for a single test | 
| # %sumcount    : line number   -> execution count for all tests | 
| # %sumfnccount : function name -> execution count for all tests | 
| +# %sumbrcount  : line number   -> branch coverage data for all tests | 
| # %funcdata    : function name -> line number | 
| # %checkdata   : line number   -> checksum of source code line | 
| +# $brdata      : vector of items: block, branch, taken | 
| # | 
| # Note that .info file sections referring to the same file and test name | 
| # will automatically be combined by adding all execution counts. | 
| @@ -1088,6 +1282,9 @@ sub read_info_file($) | 
| my $testfncdata; | 
| my $testfnccount; | 
| my $sumfnccount; | 
| +	my $testbrdata; | 
| +	my $testbrcount; | 
| +	my $sumbrcount; | 
| my $line;			# Current line read from .info file | 
| my $testname;			# Current test name | 
| my $filename;			# Current filename | 
| @@ -1096,6 +1293,8 @@ sub read_info_file($) | 
| my $negative;			# If set, warn about negative counts | 
| my $changed_testname;		# If set, warn about changed testname | 
| my $line_checksum;		# Checksum of current line | 
| +	my $br_found; | 
| +	my $br_hit; | 
| local *INFO_HANDLE;		# Filehandle for .info file | 
|  | 
| info("Reading data file $tracefile\n"); | 
| @@ -1126,14 +1325,14 @@ sub read_info_file($) | 
| "compressed file $_[0]!\n"); | 
|  | 
| # Open compressed file | 
| -		open(INFO_HANDLE, "gunzip -c $_[0]|") | 
| +		open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") | 
| or die("ERROR: cannot start gunzip to decompress ". | 
| "file $_[0]!\n"); | 
| } | 
| else | 
| { | 
| # Open decompressed file | 
| -		open(INFO_HANDLE, $_[0]) | 
| +		open(INFO_HANDLE, "<", $_[0]) | 
| or die("ERROR: cannot read file $_[0]!\n"); | 
| } | 
|  | 
| @@ -1146,7 +1345,7 @@ sub read_info_file($) | 
| # Switch statement | 
| foreach ($line) | 
| { | 
| -			/^TN:([^,]*)/ && do | 
| +			/^TN:([^,]*)(,diff)?/ && do | 
| { | 
| # Test name information found | 
| $testname = defined($1) ? $1 : ""; | 
| @@ -1154,6 +1353,7 @@ sub read_info_file($) | 
| { | 
| $changed_testname = 1; | 
| } | 
| +				$testname .= $2 if (defined($2)); | 
| last; | 
| }; | 
|  | 
| @@ -1165,18 +1365,21 @@ sub read_info_file($) | 
|  | 
| $data = $result{$filename}; | 
| ($testdata, $sumcount, $funcdata, $checkdata, | 
| -				 $testfncdata, $sumfnccount) = | 
| +				 $testfncdata, $sumfnccount, $testbrdata, | 
| +				 $sumbrcount) = | 
| get_info_entry($data); | 
|  | 
| if (defined($testname)) | 
| { | 
| $testcount = $testdata->{$testname}; | 
| $testfnccount = $testfncdata->{$testname}; | 
| +					$testbrcount = $testbrdata->{$testname}; | 
| } | 
| else | 
| { | 
| $testcount = {}; | 
| $testfnccount = {}; | 
| +					$testbrcount = {}; | 
| } | 
| last; | 
| }; | 
| @@ -1220,6 +1423,8 @@ sub read_info_file($) | 
|  | 
| /^FN:(\d+),([^,]+)/ && do | 
| { | 
| +				last if (!$func_coverage); | 
| + | 
| # Function data found, add to structure | 
| $funcdata->{$2} = $1; | 
|  | 
| @@ -1238,6 +1443,7 @@ sub read_info_file($) | 
|  | 
| /^FNDA:(\d+),([^,]+)/ && do | 
| { | 
| +				last if (!$func_coverage); | 
| # Function call count found, add to structure | 
| # Add summary counts | 
| $sumfnccount->{$2} += $1; | 
| @@ -1249,6 +1455,28 @@ sub read_info_file($) | 
| } | 
| last; | 
| }; | 
| + | 
| +			/^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { | 
| +				# Branch coverage data found | 
| +				my ($line, $block, $branch, $taken) = | 
| +				   ($1, $2, $3, $4); | 
| + | 
| +				last if (!$br_coverage); | 
| +				$sumbrcount->{$line} = | 
| +					br_ivec_push($sumbrcount->{$line}, | 
| +						     $block, $branch, $taken); | 
| + | 
| +				# Add test-specific counts | 
| +				if (defined($testname)) { | 
| +					$testbrcount->{$line} = | 
| +						br_ivec_push( | 
| +							$testbrcount->{$line}, | 
| +							$block, $branch, | 
| +							$taken); | 
| +				} | 
| +				last; | 
| +			}; | 
| + | 
| /^end_of_record/ && do | 
| { | 
| # Found end of section marker | 
| @@ -1261,12 +1489,16 @@ sub read_info_file($) | 
| $testcount; | 
| $testfncdata->{$testname} = | 
| $testfnccount; | 
| +						$testbrdata->{$testname} = | 
| +							$testbrcount; | 
| } | 
|  | 
| set_info_entry($data, $testdata, | 
| $sumcount, $funcdata, | 
| $checkdata, $testfncdata, | 
| -						       $sumfnccount); | 
| +						       $sumfnccount, | 
| +						       $testbrdata, | 
| +						       $sumbrcount); | 
| $result{$filename} = $data; | 
| last; | 
| } | 
| @@ -1284,7 +1516,8 @@ sub read_info_file($) | 
| $data = $result{$filename}; | 
|  | 
| ($testdata, $sumcount, undef, undef, $testfncdata, | 
| -		 $sumfnccount) = get_info_entry($data); | 
| +		 $sumfnccount, $testbrdata, $sumbrcount) = | 
| +			get_info_entry($data); | 
|  | 
| # Filter out empty files | 
| if (scalar(keys(%{$sumcount})) == 0) | 
| @@ -1323,6 +1556,12 @@ sub read_info_file($) | 
| } | 
| } | 
| $data->{"f_hit"} = $hitcount; | 
| + | 
| +		# Get found/hit values for branch data | 
| +		($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); | 
| + | 
| +		$data->{"b_found"} = $br_found; | 
| +		$data->{"b_hit"} = $br_hit; | 
| } | 
|  | 
| if (scalar(keys(%result)) == 0) | 
| @@ -1362,26 +1601,32 @@ sub get_info_entry($) | 
| my $checkdata_ref = $_[0]->{"check"}; | 
| my $testfncdata = $_[0]->{"testfnc"}; | 
| my $sumfnccount = $_[0]->{"sumfnc"}; | 
| +	my $testbrdata = $_[0]->{"testbr"}; | 
| +	my $sumbrcount = $_[0]->{"sumbr"}; | 
| my $lines_found = $_[0]->{"found"}; | 
| my $lines_hit = $_[0]->{"hit"}; | 
| my $fn_found = $_[0]->{"f_found"}; | 
| my $fn_hit = $_[0]->{"f_hit"}; | 
| +	my $br_found = $_[0]->{"b_found"}; | 
| +	my $br_hit = $_[0]->{"b_hit"}; | 
|  | 
| return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, | 
| -		$testfncdata, $sumfnccount, $lines_found, $lines_hit, | 
| -		$fn_found, $fn_hit); | 
| +		$testfncdata, $sumfnccount, $testbrdata, $sumbrcount, | 
| +		$lines_found, $lines_hit, $fn_found, $fn_hit, | 
| +		$br_found, $br_hit); | 
| } | 
|  | 
|  | 
| # | 
| # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref, | 
| -#                checkdata_ref, testfncdata_ref, sumfcncount_ref[,lines_found, | 
| -#                lines_hit, f_found, f_hit]) | 
| +#                checkdata_ref, testfncdata_ref, sumfcncount_ref, | 
| +#                testbrdata_ref, sumbrcount_ref[,lines_found, | 
| +#                lines_hit, f_found, f_hit, $b_found, $b_hit]) | 
| # | 
| # Update the hash referenced by HASH_REF with the provided data references. | 
| # | 
|  | 
| -sub set_info_entry($$$$$$$;$$$$) | 
| +sub set_info_entry($$$$$$$$$;$$$$$$) | 
| { | 
| my $data_ref = $_[0]; | 
|  | 
| @@ -1391,11 +1636,15 @@ sub set_info_entry($$$$$$$;$$$$) | 
| $data_ref->{"check"} = $_[4]; | 
| $data_ref->{"testfnc"} = $_[5]; | 
| $data_ref->{"sumfnc"} = $_[6]; | 
| - | 
| -	if (defined($_[7])) { $data_ref->{"found"} = $_[7]; } | 
| -	if (defined($_[8])) { $data_ref->{"hit"} = $_[8]; } | 
| -	if (defined($_[9])) { $data_ref->{"f_found"} = $_[9]; } | 
| -	if (defined($_[10])) { $data_ref->{"f_hit"} = $_[10]; } | 
| +	$data_ref->{"testbr"} = $_[7]; | 
| +	$data_ref->{"sumbr"} = $_[8]; | 
| + | 
| +	if (defined($_[9])) { $data_ref->{"found"} = $_[9]; } | 
| +	if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; } | 
| +	if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; } | 
| +	if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; } | 
| +	if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; } | 
| +	if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; } | 
| } | 
|  | 
|  | 
| @@ -1503,7 +1752,9 @@ sub merge_func_data($$$) | 
| my %result; | 
| my $func; | 
|  | 
| -	%result = %{$funcdata1}; | 
| +	if (defined($funcdata1)) { | 
| +		%result = %{$funcdata1}; | 
| +	} | 
|  | 
| foreach $func (keys(%{$funcdata2})) { | 
| my $line1 = $result{$func}; | 
| @@ -1535,7 +1786,9 @@ sub add_fnccount($$) | 
| my $fn_hit; | 
| my $function; | 
|  | 
| -	%result = %{$fnccount1}; | 
| +	if (defined($fnccount1)) { | 
| +		%result = %{$fnccount1}; | 
| +	} | 
| foreach $function (keys(%{$fnccount2})) { | 
| $result{$function} += $fnccount2->{$function}; | 
| } | 
| @@ -1589,6 +1842,167 @@ sub add_testfncdata($$) | 
| return \%result; | 
| } | 
|  | 
| + | 
| +# | 
| +# brcount_to_db(brcount) | 
| +# | 
| +# Convert brcount data to the following format: | 
| +# | 
| +# db:          line number    -> block hash | 
| +# block hash:  block number   -> branch hash | 
| +# branch hash: branch number  -> taken value | 
| +# | 
| + | 
| +sub brcount_to_db($) | 
| +{ | 
| +	my ($brcount) = @_; | 
| +	my $line; | 
| +	my $db; | 
| + | 
| +	# Add branches from first count to database | 
| +	foreach $line (keys(%{$brcount})) { | 
| +		my $brdata = $brcount->{$line}; | 
| +		my $i; | 
| +		my $num = br_ivec_len($brdata); | 
| + | 
| +		for ($i = 0; $i < $num; $i++) { | 
| +			my ($block, $branch, $taken) = br_ivec_get($brdata, $i); | 
| + | 
| +			$db->{$line}->{$block}->{$branch} = $taken; | 
| +		} | 
| +	} | 
| + | 
| +	return $db; | 
| +} | 
| + | 
| + | 
| +# | 
| +# db_to_brcount(db) | 
| +# | 
| +# Convert branch coverage data back to brcount format. | 
| +# | 
| + | 
| +sub db_to_brcount($) | 
| +{ | 
| +	my ($db) = @_; | 
| +	my $line; | 
| +	my $brcount = {}; | 
| +	my $br_found = 0; | 
| +	my $br_hit = 0; | 
| + | 
| +	# Convert database back to brcount format | 
| +	foreach $line (sort({$a <=> $b} keys(%{$db}))) { | 
| +		my $ldata = $db->{$line}; | 
| +		my $brdata; | 
| +		my $block; | 
| + | 
| +		foreach $block (sort({$a <=> $b} keys(%{$ldata}))) { | 
| +			my $bdata = $ldata->{$block}; | 
| +			my $branch; | 
| + | 
| +			foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) { | 
| +				my $taken = $bdata->{$branch}; | 
| + | 
| +				$br_found++; | 
| +				$br_hit++ if ($taken ne "-" && $taken > 0); | 
| +				$brdata = br_ivec_push($brdata, $block, | 
| +						       $branch, $taken); | 
| +			} | 
| +		} | 
| +		$brcount->{$line} = $brdata; | 
| +	} | 
| + | 
| +	return ($brcount, $br_found, $br_hit); | 
| +} | 
| + | 
| + | 
| +# | 
| +# combine_brcount(brcount1, brcount2, type) | 
| +# | 
| +# If add is BR_ADD, add branch coverage data and return list (brcount_added, | 
| +# br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2 | 
| +# from brcount1 and return (brcount_sub, br_found, br_hit). | 
| +# | 
| + | 
| +sub combine_brcount($$$) | 
| +{ | 
| +	my ($brcount1, $brcount2, $type) = @_; | 
| +	my $line; | 
| +	my $block; | 
| +	my $branch; | 
| +	my $taken; | 
| +	my $db; | 
| +	my $br_found = 0; | 
| +	my $br_hit = 0; | 
| +	my $result; | 
| + | 
| +	# Convert branches from first count to database | 
| +	$db = brcount_to_db($brcount1); | 
| +	# Combine values from database and second count | 
| +	foreach $line (keys(%{$brcount2})) { | 
| +		my $brdata = $brcount2->{$line}; | 
| +		my $num = br_ivec_len($brdata); | 
| +		my $i; | 
| + | 
| +		for ($i = 0; $i < $num; $i++) { | 
| +			($block, $branch, $taken) = br_ivec_get($brdata, $i); | 
| +			my $new_taken = $db->{$line}->{$block}->{$branch}; | 
| + | 
| +			if ($type == $BR_ADD) { | 
| +				$new_taken = br_taken_add($new_taken, $taken); | 
| +			} elsif ($type == $BR_SUB) { | 
| +				$new_taken = br_taken_sub($new_taken, $taken); | 
| +			} | 
| +			$db->{$line}->{$block}->{$branch} = $new_taken | 
| +				if (defined($new_taken)); | 
| +		} | 
| +	} | 
| +	# Convert database back to brcount format | 
| +	($result, $br_found, $br_hit) = db_to_brcount($db); | 
| + | 
| +	return ($result, $br_found, $br_hit); | 
| +} | 
| + | 
| + | 
| +# | 
| +# add_testbrdata(testbrdata1, testbrdata2) | 
| +# | 
| +# Add branch coverage data for several tests. Return reference to | 
| +# added_testbrdata. | 
| +# | 
| + | 
| +sub add_testbrdata($$) | 
| +{ | 
| +	my ($testbrdata1, $testbrdata2) = @_; | 
| +	my %result; | 
| +	my $testname; | 
| + | 
| +	foreach $testname (keys(%{$testbrdata1})) { | 
| +		if (defined($testbrdata2->{$testname})) { | 
| +			my $brcount; | 
| + | 
| +			# Branch coverage data for this testname exists | 
| +			# in both data sets: add | 
| +			($brcount) = combine_brcount($testbrdata1->{$testname}, | 
| +					 $testbrdata2->{$testname}, $BR_ADD); | 
| +			$result{$testname} = $brcount; | 
| +			next; | 
| +		} | 
| +		# Branch coverage data for this testname is unique to | 
| +		# data set 1: copy | 
| +		$result{$testname} = $testbrdata1->{$testname}; | 
| +	} | 
| + | 
| +	# Add count data for testnames unique to data set 2 | 
| +	foreach $testname (keys(%{$testbrdata2})) { | 
| +		if (!defined($result{$testname})) { | 
| +			$result{$testname} = $testbrdata2->{$testname}; | 
| +		} | 
| +	} | 
| +	return \%result; | 
| +} | 
| + | 
| + | 
| # | 
| # combine_info_entries(entry_ref1, entry_ref2, filename) | 
| # | 
| @@ -1605,6 +2019,8 @@ sub combine_info_entries($$$) | 
| my $checkdata1; | 
| my $testfncdata1; | 
| my $sumfnccount1; | 
| +	my $testbrdata1; | 
| +	my $sumbrcount1; | 
|  | 
| my $entry2 = $_[1];	# Reference to hash containing second entry | 
| my $testdata2; | 
| @@ -1613,6 +2029,8 @@ sub combine_info_entries($$$) | 
| my $checkdata2; | 
| my $testfncdata2; | 
| my $sumfnccount2; | 
| +	my $testbrdata2; | 
| +	my $sumbrcount2; | 
|  | 
| my %result;		# Hash containing combined entry | 
| my %result_testdata; | 
| @@ -1620,19 +2038,23 @@ sub combine_info_entries($$$) | 
| my $result_funcdata; | 
| my $result_testfncdata; | 
| my $result_sumfnccount; | 
| +	my $result_testbrdata; | 
| +	my $result_sumbrcount; | 
| my $lines_found; | 
| my $lines_hit; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
|  | 
| my $testname; | 
| my $filename = $_[2]; | 
|  | 
| # Retrieve data | 
| ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, | 
| -	 $sumfnccount1) = get_info_entry($entry1); | 
| +	 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1); | 
| ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, | 
| -	 $sumfnccount2) = get_info_entry($entry2); | 
| +	 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2); | 
|  | 
| # Merge checksums | 
| $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); | 
| @@ -1645,6 +2067,11 @@ sub combine_info_entries($$$) | 
| ($result_sumfnccount, $fn_found, $fn_hit) = | 
| add_fnccount($sumfnccount1, $sumfnccount2); | 
|  | 
| +	# Combine branch coverage data | 
| +	$result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2); | 
| +	($result_sumbrcount, $br_found, $br_hit) = | 
| +		combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD); | 
| + | 
| # Combine testdata | 
| foreach $testname (keys(%{$testdata1})) | 
| { | 
| @@ -1687,8 +2114,9 @@ sub combine_info_entries($$$) | 
| # Store result | 
| set_info_entry(\%result, \%result_testdata, $result_sumcount, | 
| $result_funcdata, $checkdata1, $result_testfncdata, | 
| -		       $result_sumfnccount, $lines_found, $lines_hit, | 
| -		       $fn_found, $fn_hit); | 
| +		       $result_sumfnccount, $result_testbrdata, | 
| +		       $result_sumbrcount, $lines_found, $lines_hit, | 
| +		       $fn_found, $fn_hit, $br_found, $br_hit); | 
|  | 
| return(\%result); | 
| } | 
| @@ -1730,16 +2158,17 @@ sub combine_info_files($$) | 
|  | 
|  | 
| # | 
| -# get_prefix(filename_list) | 
| +# get_prefix(min_dir, filename_list) | 
| # | 
| # Search FILENAME_LIST for a directory prefix which is common to as many | 
| # list entries as possible, so that removing this prefix will minimize the | 
| -# sum of the lengths of all resulting shortened filenames. | 
| +# sum of the lengths of all resulting shortened filenames while observing | 
| +# that no filename has less than MIN_DIR parent directories. | 
| # | 
|  | 
| -sub get_prefix(@) | 
| +sub get_prefix($@) | 
| { | 
| -	my @filename_list = @_;		# provided list of filenames | 
| +	my ($min_dir, @filename_list) = @_; | 
| my %prefix;			# mapping: prefix -> sum of lengths | 
| my $current;			# Temporary iteration variable | 
|  | 
| @@ -1748,12 +2177,14 @@ sub get_prefix(@) | 
| { | 
| # Need explicit assignment to get a copy of $_ so that | 
| # shortening the contained prefix does not affect the list | 
| -		$current = shorten_prefix($_); | 
| +		$current = $_; | 
| while ($current = shorten_prefix($current)) | 
| { | 
| +			$current .= "/"; | 
| + | 
| # Skip rest if the remaining prefix has already been | 
| # added to hash | 
| -			if ($prefix{$current}) { last; } | 
| +			if (exists($prefix{$current})) { last; } | 
|  | 
| # Initialize with 0 | 
| $prefix{$current}="0"; | 
| @@ -1761,6 +2192,20 @@ sub get_prefix(@) | 
|  | 
| } | 
|  | 
| +	# Remove all prefixes that would cause filenames to have less than | 
| +	# the minimum number of parent directories | 
| +	foreach my $filename (@filename_list) { | 
| +		my $dir = dirname($filename); | 
| + | 
| +		for (my $i = 0; $i < $min_dir; $i++) { | 
| +			delete($prefix{$dir."/"}); | 
| +			$dir = shorten_prefix($dir); | 
| +		} | 
| +	} | 
| + | 
| +	# Check if any prefix remains | 
| +	return undef if (!%prefix); | 
| + | 
| # Calculate sum of lengths for all prefixes | 
| foreach $current (keys(%prefix)) | 
| { | 
| @@ -1789,6 +2234,8 @@ sub get_prefix(@) | 
| } | 
| } | 
|  | 
| +	$current =~ s/\/$//; | 
| + | 
| return($current); | 
| } | 
|  | 
| @@ -1880,7 +2327,7 @@ sub read_testfile($) | 
| my $changed_testname; | 
| local *TEST_HANDLE; | 
|  | 
| -	open(TEST_HANDLE, "<".$_[0]) | 
| +	open(TEST_HANDLE, "<", $_[0]) | 
| or die("ERROR: cannot open $_[0]!\n"); | 
|  | 
| while (<TEST_HANDLE>) | 
| @@ -1986,14 +2433,17 @@ sub get_date_string() | 
|  | 
| sub create_sub_dir($) | 
| { | 
| -	system("mkdir", "-p" ,$_[0]) | 
| -		and die("ERROR: cannot create directory $_!\n"); | 
| +	my ($dir) = @_; | 
| + | 
| +	system("mkdir", "-p" ,$dir) | 
| +		and die("ERROR: cannot create directory $dir!\n"); | 
| } | 
|  | 
|  | 
| # | 
| # write_description_file(descriptions, overall_found, overall_hit, | 
| -#                        total_fn_found, total_fn_hit) | 
| +#                        total_fn_found, total_fn_hit, total_br_found, | 
| +#                        total_br_hit) | 
| # | 
| # Write HTML file containing all test case descriptions. DESCRIPTIONS is a | 
| # reference to a hash containing a mapping | 
| @@ -2003,20 +2453,22 @@ sub create_sub_dir($) | 
| # Die on error. | 
| # | 
|  | 
| -sub write_description_file($$$$$) | 
| +sub write_description_file($$$$$$$) | 
| { | 
| my %description = %{$_[0]}; | 
| my $found = $_[1]; | 
| my $hit = $_[2]; | 
| my $fn_found = $_[3]; | 
| my $fn_hit = $_[4]; | 
| +	my $br_found = $_[5]; | 
| +	my $br_hit = $_[6]; | 
| my $test_name; | 
| local *HTML_HANDLE; | 
|  | 
| html_create(*HTML_HANDLE,"descriptions.$html_ext"); | 
| write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions"); | 
| write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found, | 
| -		     $fn_hit, 0); | 
| +		     $fn_hit, $br_found, $br_hit, 0); | 
|  | 
| write_test_table_prolog(*HTML_HANDLE, | 
| "Test case descriptions - alphabetical list"); | 
| @@ -2146,7 +2598,7 @@ sub write_png_files() | 
| 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort); | 
| foreach (keys(%data)) | 
| { | 
| -		open(PNG_HANDLE, ">".$_) | 
| +		open(PNG_HANDLE, ">", $_) | 
| or die("ERROR: cannot create $_!\n"); | 
| binmode(PNG_HANDLE); | 
| print(PNG_HANDLE map(chr,@{$data{$_}})); | 
| @@ -2164,7 +2616,7 @@ sub write_htaccess_file() | 
| local *HTACCESS_HANDLE; | 
| my $htaccess_data; | 
|  | 
| -	open(*HTACCESS_HANDLE, ">.htaccess") | 
| +	open(*HTACCESS_HANDLE, ">", ".htaccess") | 
| or die("ERROR: cannot open .htaccess for writing!\n"); | 
|  | 
| $htaccess_data = (<<"END_OF_HTACCESS") | 
| @@ -2197,7 +2649,7 @@ sub write_css_file() | 
| return; | 
| } | 
|  | 
| -	open(CSS_HANDLE, ">gcov.css") | 
| +	open(CSS_HANDLE, ">", "gcov.css") | 
| or die ("ERROR: cannot open gcov.css for writing!\n"); | 
|  | 
|  | 
| @@ -2328,17 +2780,6 @@ sub write_css_file() | 
| background-color: #FF0000; | 
| } | 
|  | 
| -	/* All views: header legend item for legend entry */ | 
| -	td.headerItemLeg | 
| -	{ | 
| -	  text-align: right; | 
| -	  padding-right: 6px; | 
| -	  font-family: sans-serif; | 
| -	  font-weight: bold; | 
| -	  vertical-align: bottom; | 
| -	  white-space: nowrap; | 
| -	} | 
| - | 
| /* All views: header legend value for legend entry */ | 
| td.headerValueLeg | 
| { | 
| @@ -2419,6 +2860,7 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #A7FC9D; | 
| font-weight: bold; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Directory view/File view (all): line count entry for files with | 
| @@ -2430,16 +2872,7 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #A7FC9D; | 
| white-space: nowrap; | 
| -	} | 
| - | 
| -	/* Directory view/File view (all): legend entry for high coverage | 
| -	   rate */ | 
| -	span.coverLegendHi | 
| -	{ | 
| -	  padding-left: 10px; | 
| -	  padding-right: 10px; | 
| -	  padding-bottom: 2px; | 
| -	  background-color: #A7FC9D; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Directory view/File view (all): percentage entry for files with | 
| @@ -2451,6 +2884,7 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #FFEA20; | 
| font-weight: bold; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Directory view/File view (all): line count entry for files with | 
| @@ -2462,16 +2896,7 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #FFEA20; | 
| white-space: nowrap; | 
| -	} | 
| - | 
| -	/* Directory view/File view (all): legend entry for medium coverage | 
| -	   rate */ | 
| -	span.coverLegendMed | 
| -	{ | 
| -	  padding-left: 10px; | 
| -	  padding-right: 10px; | 
| -	  padding-bottom: 2px; | 
| -	  background-color: #FFEA20; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Directory view/File view (all): percentage entry for files with | 
| @@ -2483,6 +2908,7 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #FF0000; | 
| font-weight: bold; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Directory view/File view (all): line count entry for files with | 
| @@ -2494,53 +2920,28 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #FF0000; | 
| white-space: nowrap; | 
| -	} | 
| - | 
| -	/* Directory view/File view (all): legend entry for low coverage | 
| -	   rate */ | 
| -	span.coverLegendLo | 
| -	{ | 
| -	  padding-left: 10px; | 
| -	  padding-right: 10px; | 
| -	  padding-bottom: 2px; | 
| -	  background-color: #FF0000; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* File view (all): "show/hide details" link format */ | 
| a.detail:link | 
| { | 
| color: #B8D0FF; | 
| +	  font-size:80%; | 
| } | 
|  | 
| /* File view (all): "show/hide details" link - visited format */ | 
| a.detail:visited | 
| { | 
| color: #B8D0FF; | 
| +	  font-size:80%; | 
| } | 
|  | 
| /* File view (all): "show/hide details" link - activated format */ | 
| a.detail:active | 
| { | 
| color: #FFFFFF; | 
| -	} | 
| - | 
| -	/* File view (detail): test name table headline format */ | 
| -	td.testNameHead | 
| -	{ | 
| -	  text-align: right; | 
| -	  padding-right: 10px; | 
| -	  background-color: #DAE7FE; | 
| -	  font-family: sans-serif; | 
| -	  font-weight: bold; | 
| -	} | 
| - | 
| -	/* File view (detail): test lines table headline format */ | 
| -	td.testLinesHead | 
| -	{ | 
| -	  text-align: center; | 
| -	  background-color: #DAE7FE; | 
| -	  font-family: sans-serif; | 
| -	  font-weight: bold; | 
| +	  font-size:80%; | 
| } | 
|  | 
| /* File view (detail): test name entry */ | 
| @@ -2549,6 +2950,7 @@ sub write_css_file() | 
| text-align: right; | 
| padding-right: 10px; | 
| background-color: #DAE7FE; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* File view (detail): test percentage entry */ | 
| @@ -2558,6 +2960,7 @@ sub write_css_file() | 
| padding-left: 10px; | 
| padding-right: 10px; | 
| background-color: #DAE7FE; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* File view (detail): test lines count entry */ | 
| @@ -2567,6 +2970,7 @@ sub write_css_file() | 
| padding-left: 10px; | 
| padding-right: 10px; | 
| background-color: #DAE7FE; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Test case descriptions: test name format*/ | 
| @@ -2605,6 +3009,7 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #FF0000; | 
| font-weight: bold; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Source code view: function entry nonzero count*/ | 
| @@ -2615,14 +3020,15 @@ sub write_css_file() | 
| padding-right: 10px; | 
| background-color: #DAE7FE; | 
| font-weight: bold; | 
| +	  font-family: sans-serif; | 
| } | 
|  | 
| /* Source code view: source code format */ | 
| -	/* Source code view: source code format */ | 
| pre.source | 
| { | 
| font-family: monospace; | 
| white-space: pre; | 
| +	  margin-top: 2px; | 
| } | 
|  | 
| /* Source code view: line number format */ | 
| @@ -2660,7 +3066,7 @@ sub write_css_file() | 
| padding-left: 10px; | 
| padding-right: 10px; | 
| padding-bottom: 2px; | 
| -	  background-color: #FF0000; | 
| +	  background-color: #FF6230; | 
| } | 
|  | 
| /* Source code view (function table): standard link - visited format */ | 
| @@ -2678,38 +3084,122 @@ sub write_css_file() | 
| background-color: #B5F7AF; | 
| } | 
|  | 
| -	/* Source code view: format for DiffCov legend */ | 
| -	span.LegendDiffCov | 
| +	/* Source code view: format for branches which were executed | 
| +	 * and taken */ | 
| +	span.branchCov | 
| { | 
| -	  text-align: center; | 
| -	  padding-left: 10px; | 
| -	  padding-right: 10px; | 
| -	  background-color: #B5F7AF; | 
| +	  background-color: #CAD7FE; | 
| } | 
| -END_OF_CSS | 
| -	; | 
| - | 
| -	# ************************************************************* | 
|  | 
| +	/* Source code view: format for branches which were executed | 
| +	 * but not taken */ | 
| +	span.branchNoCov | 
| +	{ | 
| +	  background-color: #FF6230; | 
| +	} | 
|  | 
| -	# Remove leading tab from all lines | 
| -	$css_data =~ s/^\t//gm; | 
| +	/* Source code view: format for branches which were not executed */ | 
| +	span.branchNoExec | 
| +	{ | 
| +	  background-color: #FF6230; | 
| +	} | 
|  | 
| -	print(CSS_HANDLE $css_data); | 
| +	/* Source code view: format for the source code heading line */ | 
| +	pre.sourceHeading | 
| +	{ | 
| +	  white-space: pre; | 
| +	  font-family: monospace; | 
| +	  font-weight: bold; | 
| +	  margin: 0px; | 
| +	} | 
|  | 
| -	close(CSS_HANDLE); | 
| -} | 
| +	/* All views: header legend value for low rate */ | 
| +	td.headerValueLegL | 
| +	{ | 
| +	  font-family: sans-serif; | 
| +	  text-align: center; | 
| +	  white-space: nowrap; | 
| +	  padding-left: 4px; | 
| +	  padding-right: 2px; | 
| +	  background-color: #FF0000; | 
| +	  font-size: 80%; | 
| +	} | 
|  | 
| +	/* All views: header legend value for med rate */ | 
| +	td.headerValueLegM | 
| +	{ | 
| +	  font-family: sans-serif; | 
| +	  text-align: center; | 
| +	  white-space: nowrap; | 
| +	  padding-left: 2px; | 
| +	  padding-right: 2px; | 
| +	  background-color: #FFEA20; | 
| +	  font-size: 80%; | 
| +	} | 
|  | 
| -# | 
| -# get_bar_graph_code(base_dir, cover_found, cover_hit) | 
| -# | 
| +	/* All views: header legend value for hi rate */ | 
| +	td.headerValueLegH | 
| +	{ | 
| +	  font-family: sans-serif; | 
| +	  text-align: center; | 
| +	  white-space: nowrap; | 
| +	  padding-left: 2px; | 
| +	  padding-right: 4px; | 
| +	  background-color: #A7FC9D; | 
| +	  font-size: 80%; | 
| +	} | 
| + | 
| +	/* All views except source code view: legend format for low coverage */ | 
| +	span.coverLegendCovLo | 
| +	{ | 
| +	  padding-left: 10px; | 
| +	  padding-right: 10px; | 
| +	  padding-top: 2px; | 
| +	  background-color: #FF0000; | 
| +	} | 
| + | 
| +	/* All views except source code view: legend format for med coverage */ | 
| +	span.coverLegendCovMed | 
| +	{ | 
| +	  padding-left: 10px; | 
| +	  padding-right: 10px; | 
| +	  padding-top: 2px; | 
| +	  background-color: #FFEA20; | 
| +	} | 
| + | 
| +	/* All views except source code view: legend format for hi coverage */ | 
| +	span.coverLegendCovHi | 
| +	{ | 
| +	  padding-left: 10px; | 
| +	  padding-right: 10px; | 
| +	  padding-top: 2px; | 
| +	  background-color: #A7FC9D; | 
| +	} | 
| +END_OF_CSS | 
| +	; | 
| + | 
| +	# ************************************************************* | 
| + | 
| + | 
| +	# Remove leading tab from all lines | 
| +	$css_data =~ s/^\t//gm; | 
| + | 
| +	print(CSS_HANDLE $css_data); | 
| + | 
| +	close(CSS_HANDLE); | 
| +} | 
| + | 
| + | 
| +# | 
| +# get_bar_graph_code(base_dir, cover_found, cover_hit) | 
| +# | 
| # Return a string containing HTML code which implements a bar graph display | 
| # for a coverage rate of cover_hit * 100 / cover_found. | 
| # | 
|  | 
| sub get_bar_graph_code($$$) | 
| { | 
| +	my ($base_dir, $found, $hit) = @_; | 
| my $rate; | 
| my $alt; | 
| my $width; | 
| @@ -2720,13 +3210,12 @@ sub get_bar_graph_code($$$) | 
| # Check number of instrumented lines | 
| if ($_[1] == 0) { return ""; } | 
|  | 
| -	$rate		= $_[2] * 100 / $_[1]; | 
| -	$alt		= sprintf("%.1f", $rate)."%"; | 
| -	$width		= sprintf("%.0f", $rate); | 
| -	$remainder	= sprintf("%d", 100-$width); | 
| +	$alt		= rate($hit, $found, "%"); | 
| +	$width		= rate($hit, $found, undef, 0); | 
| +	$remainder	= 100 - $width; | 
|  | 
| # Decide which .png file to use | 
| -	$png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit, | 
| +	$png_name = $rate_png[classify_rate($found, $hit, $med_limit, | 
| $hi_limit)]; | 
|  | 
| if ($width == 0) | 
| @@ -2775,7 +3264,7 @@ sub classify_rate($$$$) | 
| if ($found == 0) { | 
| return 2; | 
| } | 
| -	$rate = $hit * 100 / $found; | 
| +	$rate = rate($hit, $found); | 
| if ($rate < $med) { | 
| return 0; | 
| } elsif ($rate < $hi) { | 
| @@ -2853,104 +3342,40 @@ END_OF_HTML | 
|  | 
|  | 
| # | 
| -# write_header_line(filehandle, type, additional params..) | 
| +# write_header_line(handle, content) | 
| # | 
| -# Write a header line. | 
| +# Write a header line with the specified table contents. | 
| # | 
|  | 
| -sub write_header_line(*$@) | 
| +sub write_header_line(*@) | 
| { | 
| -	my $HANDLE = shift; | 
| -	my $type = shift; | 
| -	my @args = @_; | 
| - | 
| -	# Reduce indentation by using gotos | 
| -	if ($type eq 0) { | 
| -		goto header; | 
| -	} elsif ($type eq 1) { | 
| -		goto body; | 
| -	} elsif ($type eq 2) { | 
| -		goto legend_dir; | 
| -	} elsif ($type eq 3) { | 
| -		goto legend_source; | 
| -	} elsif ($type eq 4) { | 
| -		goto half_body; | 
| -	} | 
| - | 
| -header: | 
| -	# ************************************************************* | 
| -	write_html($HANDLE, <<END_OF_HTML); | 
| -        <tr> | 
| -          <td width="5%"></td> | 
| -          <td width="10%" class="headerItem">$args[0]</td> | 
| -          <td width="35%" class="headerValue">$args[1]</td> | 
| -          <td width="10%"></td> | 
| -          <td width="10%" class="headerCovTableHead">$args[2]</td> | 
| -          <td width="10%" class="headerCovTableHead">$args[3]</td> | 
| -          <td width="15%" class="headerCovTableHead">$args[4]</td> | 
| -          <td width="5%"></td> | 
| -        </tr> | 
| -END_OF_HTML | 
| -	# ************************************************************* | 
| -	return; | 
| +	my ($handle, @content) = @_; | 
| +	my $entry; | 
|  | 
| -body: | 
| -	# ************************************************************* | 
| -	write_html($HANDLE, <<END_OF_HTML); | 
| -        <tr> | 
| -          <td></td> | 
| -          <td class="headerItem">$args[0]</td> | 
| -          <td class="headerValue">$args[1]</td> | 
| -          <td class="headerItem">$args[2]</td> | 
| -          <td class="headerCovTableEntry">$args[3]</td> | 
| -          <td class="headerCovTableEntry">$args[4]</td> | 
| -          <td class="headerCovTableEntry$args[5]">$args[6]</td> | 
| -        </tr> | 
| -END_OF_HTML | 
| -	# ************************************************************* | 
| -	return; | 
| - | 
| -half_body: | 
| -	# ************************************************************* | 
| -	write_html($HANDLE, <<END_OF_HTML); | 
| -        <tr> | 
| -          <td></td> | 
| -          <td class="headerItem">$args[0]</td> | 
| -          <td class="headerValue">$args[1]</td> | 
| -        </tr> | 
| -END_OF_HTML | 
| -	# ************************************************************* | 
| -	return; | 
| +	write_html($handle, "          <tr>\n"); | 
| +	foreach $entry (@content) { | 
| +		my ($width, $class, $text, $colspan) = @{$entry}; | 
|  | 
| -legend_dir: | 
| -	# ************************************************************* | 
| -	write_html($HANDLE, <<END_OF_HTML); | 
| -        <tr> | 
| -          <td></td> | 
| -          <td class="headerItemLeg">$args[0]</td> | 
| -          <td class="headerValueLeg"> | 
| -$args[1]          </td> | 
| -          <td></td> | 
| -          <td class="headerValueLeg" colspan=3> | 
| -$args[2]          </td> | 
| -        </tr> | 
| -END_OF_HTML | 
| -	# ************************************************************* | 
| -	return; | 
| - | 
| -legend_source: | 
| -	# ************************************************************* | 
| -	write_html($HANDLE, <<END_OF_HTML); | 
| -        <tr> | 
| -          <td></td> | 
| -          <td class="headerItem">$args[0]</td> | 
| -          <td class="headerValueLeg" colspan=5> | 
| -            <span class="coverLegendNoCov">$args[1]</span> | 
| -            <span class="coverLegendCov">$args[2]</span> | 
| -          </td> | 
| -        </tr> | 
| -END_OF_HTML | 
| -	# ************************************************************* | 
| +		if (defined($width)) { | 
| +			$width = " width=\"$width\""; | 
| +		} else { | 
| +			$width = ""; | 
| +		} | 
| +		if (defined($class)) { | 
| +			$class = " class=\"$class\""; | 
| +		} else { | 
| +			$class = ""; | 
| +		} | 
| +		if (defined($colspan)) { | 
| +			$colspan = " colspan=\"$colspan\""; | 
| +		} else { | 
| +			$colspan = ""; | 
| +		} | 
| +		$text = "" if (!defined($text)); | 
| +		write_html($handle, | 
| +			   "            <td$width$class$colspan>$text</td>\n"); | 
| +	} | 
| +	write_html($handle, "          </tr>\n"); | 
| } | 
|  | 
|  | 
| @@ -2965,10 +3390,11 @@ sub write_header_epilog(*$) | 
| # ************************************************************* | 
|  | 
| write_html($_[0], <<END_OF_HTML) | 
| -                <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> | 
| +	          <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> | 
| </table> | 
| </td> | 
| </tr> | 
| + | 
| <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> | 
| </table> | 
|  | 
| @@ -2980,244 +3406,170 @@ END_OF_HTML | 
|  | 
|  | 
| # | 
| -# write_file_table_prolog(filehandle, file_heading, lines_heading, func_heading) | 
| +# write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...)) | 
| # | 
| # Write heading for file table. | 
| # | 
|  | 
| -sub write_file_table_prolog(*$$$) | 
| +sub write_file_table_prolog(*$@) | 
| { | 
| -	# ************************************************************* | 
| +	my ($handle, $file_heading, @columns) = @_; | 
| +	my $num_columns = 0; | 
| +	my $file_width; | 
| +	my $col; | 
| +	my $width; | 
|  | 
| -        if ($func_coverage) | 
| -        { | 
| -                write_html($_[0], <<END_OF_HTML) | 
| -	  <center> | 
| -	  <table width="80%" cellpadding=1 cellspacing=1 border=0> | 
| +	$width = 20 if (scalar(@columns) == 1); | 
| +	$width = 10 if (scalar(@columns) == 2); | 
| +	$width = 8 if (scalar(@columns) > 2); | 
|  | 
| -	    <tr> | 
| -	      <td width="45%"><br></td> | 
| -	      <td width="15%"></td> | 
| -	      <td width="10%"></td> | 
| -	      <td width="10%"></td> | 
| -	      <td width="10%"></td> | 
| -	      <td width="10%"></td> | 
| -	    </tr> | 
| +	foreach $col (@columns) { | 
| +		my ($heading, $cols) = @{$col}; | 
|  | 
| -	    <tr> | 
| -	      <td class="tableHead">$_[1]</td> | 
| -	      <td class="tableHead" colspan=3>$_[2]</td> | 
| -	      <td class="tableHead" colspan=2>$_[3]</td> | 
| -	    </tr> | 
| +		$num_columns += $cols; | 
| +	} | 
| +	$file_width = 100 - $num_columns * $width; | 
|  | 
| -END_OF_HTML | 
| -                ; | 
| -        } | 
| -        else | 
| -        { | 
| -                write_html($_[0], <<END_OF_HTML) | 
| +	# Table definition | 
| +	write_html($handle, <<END_OF_HTML); | 
| <center> | 
| <table width="80%" cellpadding=1 cellspacing=1 border=0> | 
|  | 
| <tr> | 
| -	      <td width="50%"><br></td> | 
| -	      <td width="15%"></td> | 
| -	      <td width="15%"></td> | 
| -	      <td width="20%"></td> | 
| +	      <td width="$file_width%"><br></td> | 
| +END_OF_HTML | 
| +	# Empty first row | 
| +	foreach $col (@columns) { | 
| +		my ($heading, $cols) = @{$col}; | 
| + | 
| +		while ($cols-- > 0) { | 
| +			write_html($handle, <<END_OF_HTML); | 
| +	      <td width="$width%"></td> | 
| +END_OF_HTML | 
| +		} | 
| +	} | 
| +	# Next row | 
| +	write_html($handle, <<END_OF_HTML); | 
| </tr> | 
|  | 
| <tr> | 
| -	      <td class="tableHead">$_[1]</td> | 
| -	      <td class="tableHead" colspan=3>$_[2]</td> | 
| +	      <td class="tableHead">$file_heading</td> | 
| +END_OF_HTML | 
| +	# Heading row | 
| +	foreach $col (@columns) { | 
| +		my ($heading, $cols) = @{$col}; | 
| +		my $colspan = ""; | 
| + | 
| +		$colspan = " colspan=$cols" if ($cols > 1); | 
| +		write_html($handle, <<END_OF_HTML); | 
| +	      <td class="tableHead"$colspan>$heading</td> | 
| +END_OF_HTML | 
| +	} | 
| +	write_html($handle, <<END_OF_HTML); | 
| </tr> | 
| - | 
| END_OF_HTML | 
| -                ; | 
| -        } | 
| - | 
| -	# ************************************************************* | 
| } | 
|  | 
|  | 
| -# | 
| -# write_file_table_entry(filehandle, cover_filename, cover_bar_graph, | 
| -#                        cover_found, cover_hit, fn_found, fn_hit, | 
| -#			 page_link, func_link) | 
| +# write_file_table_entry(handle, base_dir, filename, page_link, | 
| +#			 ([ found, hit, med_limit, hi_limit, graph ], ..) | 
| # | 
| # Write an entry of the file table. | 
| # | 
|  | 
| -sub write_file_table_entry(*$$$$$$$) | 
| +sub write_file_table_entry(*$$$@) | 
| { | 
| -	local *HANDLE = shift; | 
| -	my ($filename, $bar_graph, $found, $hit, $fn_found, $fn_hit, | 
| -	    $page_link) = @_; | 
| -	my $rate; | 
| -	my $rate_string; | 
| -	my $funcs_string; | 
| -	my $class_lines = "Lo"; | 
| -	my $class_funcs = "Hi"; | 
| +	my ($handle, $base_dir, $filename, $page_link, @entries) = @_; | 
| my $file_code; | 
| +	my $entry; | 
| +	my $esc_filename = escape_html($filename); | 
|  | 
| # Add link to source if provided | 
| if (defined($page_link) && $page_link ne "") { | 
| -		$file_code = "<a href=\"$page_link\">$filename</a>"; | 
| +		$file_code = "<a href=\"$page_link\">$esc_filename</a>"; | 
| } else { | 
| -		$file_code = $filename; | 
| +		$file_code = $esc_filename; | 
| } | 
|  | 
| -	# Get line coverage rate | 
| -	if ($found > 0) | 
| -	{ | 
| -		$rate = $hit * 100 / $found; | 
| -		$rate_string = sprintf("%.1f", $rate)." %"; | 
| - | 
| -		$class_lines = $rate_name[classify_rate($found, $hit, | 
| -					  $med_limit, $hi_limit)]; | 
| -	} | 
| -	else | 
| -	{ | 
| -		$rate_string = "-"; | 
| -	} | 
| - | 
| -	# Get function coverage rate | 
| -	if ($fn_found > 0) | 
| -	{ | 
| -		$rate = $fn_hit * 100 / $fn_found; | 
| -		$class_funcs = $rate_name[classify_rate($fn_found, $fn_hit, | 
| -					  $fn_med_limit, $fn_hi_limit)]; | 
| -		$funcs_string = sprintf("%.1f", $rate)." %"; | 
| -	} | 
| -	else | 
| -	{ | 
| -		# Define 0 of 0 functions as 100% | 
| -		$rate = 100; | 
| -		$funcs_string = "-"; | 
| -	} | 
| - | 
| -	# ************************************************************* | 
| - | 
| -	write_html(*HANDLE, <<END_OF_HTML) | 
| +	# First column: filename | 
| +	write_html($handle, <<END_OF_HTML); | 
| <tr> | 
| <td class="coverFile">$file_code</td> | 
| +END_OF_HTML | 
| +	# Columns as defined | 
| +	foreach $entry (@entries) { | 
| +		my ($found, $hit, $med, $hi, $graph) = @{$entry}; | 
| +		my $bar_graph; | 
| +		my $class; | 
| +		my $rate; | 
| + | 
| +		# Generate bar graph if requested | 
| +		if ($graph) { | 
| +			$bar_graph = get_bar_graph_code($base_dir, $found, | 
| +							$hit); | 
| +			write_html($handle, <<END_OF_HTML); | 
| <td class="coverBar" align="center"> | 
| $bar_graph | 
| </td> | 
| -	      <td class="coverPer$class_lines">$rate_string</td> | 
| -	      <td class="coverNum$class_lines">$hit / $found</td> | 
| END_OF_HTML | 
| -	; | 
| - | 
| -        if ($func_coverage) | 
| -        { | 
| -                write_html(*HANDLE, <<END_OF_HTML) | 
| -	      <td class="coverPer$class_funcs">$funcs_string</td> | 
| -	      <td class="coverNum$class_funcs">$fn_hit / $fn_found</td> | 
| -END_OF_HTML | 
| -	        ; | 
| -        } | 
| -        write_html(*HANDLE, <<END_OF_HTML) | 
| -	    </tr> | 
| +		} | 
| +		# Get rate color and text | 
| +		if ($found == 0) { | 
| +			$rate = "-"; | 
| +			$class = "Hi"; | 
| +		} else { | 
| +			$rate = rate($hit, $found, " %"); | 
| +			$class = $rate_name[classify_rate($found, $hit, | 
| +					    $med, $hi)]; | 
| +		} | 
| +		write_html($handle, <<END_OF_HTML); | 
| +	      <td class="coverPer$class">$rate</td> | 
| +	      <td class="coverNum$class">$hit / $found</td> | 
| END_OF_HTML | 
| -        ; | 
| - | 
| -	# ************************************************************* | 
| -} | 
| - | 
| - | 
| -# | 
| -# write_file_table_detail_heading(filehandle, left_heading, right_heading) | 
| -# | 
| -# Write heading for detail section in file table. | 
| -# | 
| - | 
| -sub write_file_table_detail_heading(*$$$) | 
| -{ | 
| -        my $func_rows = ""; | 
| - | 
| -        if ($func_coverage) | 
| -        { | 
| -                $func_rows = "<td class=\"testLinesHead\" colspan=2>$_[3]</td>"; | 
| -        } | 
| - | 
| -	# ************************************************************* | 
| -	write_html($_[0], <<END_OF_HTML) | 
| -	    <tr> | 
| -	      <td class="testNameHead" colspan=2>$_[1]</td> | 
| -	      <td class="testLinesHead" colspan=2>$_[2]</td> | 
| -              $func_rows | 
| +	} | 
| +	# End of row | 
| +        write_html($handle, <<END_OF_HTML); | 
| </tr> | 
| - | 
| END_OF_HTML | 
| -	; | 
| - | 
| -	# ************************************************************* | 
| } | 
|  | 
|  | 
| # | 
| -# write_file_table_detail_entry(filehandle, test_name, | 
| -#               cover_found, cover_hit, func_found, func_hit) | 
| +# write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...)) | 
| # | 
| # Write entry for detail section in file table. | 
| # | 
|  | 
| -sub write_file_table_detail_entry(*$$$$$) | 
| +sub write_file_table_detail_entry(*$@) | 
| { | 
| -	my $rate; | 
| -	my $func_rate; | 
| -	my $name = $_[1]; | 
| - | 
| -	if ($_[2]>0) | 
| -	{ | 
| -		$rate = sprintf("%.1f", $_[3]*100/$_[2])." %"; | 
| -	} | 
| -	else | 
| -	{ | 
| -		$rate = "-"; | 
| -	} | 
| - | 
| -	if ($_[4]>0) | 
| -	{ | 
| -		$func_rate = sprintf("%.1f", $_[5]*100/$_[4])." %"; | 
| -	} | 
| -	else | 
| -	{ | 
| -		$func_rate = "-"; | 
| -	} | 
| +	my ($handle, $test, @entries) = @_; | 
| +	my $entry; | 
|  | 
| -	if ($name =~ /^(.*),diff$/) | 
| -	{ | 
| -		$name = $1." (converted)"; | 
| -	} | 
| - | 
| -	if ($name eq "") | 
| -	{ | 
| -		$name = "<span style=\"font-style:italic\"><unnamed></span>"; | 
| +	if ($test eq "") { | 
| +		$test = "<span style=\"font-style:italic\"><unnamed></span>"; | 
| +	} elsif ($test =~ /^(.*),diff$/) { | 
| +		$test = $1." (converted)"; | 
| } | 
| - | 
| -	# ************************************************************* | 
| - | 
| -	write_html($_[0], <<END_OF_HTML) | 
| +	# Testname | 
| +	write_html($handle, <<END_OF_HTML); | 
| <tr> | 
| -	      <td class="testName" colspan=2>$name</td> | 
| -	      <td class="testPer">$rate</td> | 
| -	      <td class="testNum">$_[3] / $_[2] lines</td> | 
| +	      <td class="testName" colspan=2>$test</td> | 
| END_OF_HTML | 
| -	; | 
| -        if ($func_coverage) | 
| -        { | 
| -                write_html($_[0], <<END_OF_HTML) | 
| -	      <td class="testPer">$func_rate</td> | 
| -	      <td class="testNum">$_[5] / $_[4]</td> | 
| +	# Test data | 
| +	foreach $entry (@entries) { | 
| +		my ($found, $hit) = @{$entry}; | 
| +		my $rate = rate($hit, $found, " %"); | 
| + | 
| +		write_html($handle, <<END_OF_HTML); | 
| +	      <td class="testPer">$rate</td> | 
| +	      <td class="testNum">$hit / $found</td> | 
| END_OF_HTML | 
| -	        ; | 
| -        } | 
| -        write_html($_[0], <<END_OF_HTML) | 
| +	} | 
| + | 
| +        write_html($handle, <<END_OF_HTML); | 
| </tr> | 
|  | 
| END_OF_HTML | 
| -        ; | 
|  | 
| # ************************************************************* | 
| } | 
| @@ -3322,6 +3674,17 @@ END_OF_HTML | 
| } | 
|  | 
|  | 
| +sub fmt_centered($$) | 
| +{ | 
| +	my ($width, $text) = @_; | 
| +	my $w0 = length($text); | 
| +	my $w1 = int(($width - $w0) / 2); | 
| +	my $w2 = $width - $w0 - $w1; | 
| + | 
| +	return (" "x$w1).$text.(" "x$w2); | 
| +} | 
| + | 
| + | 
| # | 
| # write_source_prolog(filehandle) | 
| # | 
| @@ -3330,6 +3693,15 @@ END_OF_HTML | 
|  | 
| sub write_source_prolog(*) | 
| { | 
| +	my $lineno_heading = "         "; | 
| +	my $branch_heading = ""; | 
| +	my $line_heading = fmt_centered($line_field_width, "Line data"); | 
| +	my $source_heading = " Source code"; | 
| + | 
| +	if ($br_coverage) { | 
| +		$branch_heading = fmt_centered($br_field_width, "Branch data"). | 
| +				  " "; | 
| +	} | 
| # ************************************************************* | 
|  | 
| write_html($_[0], <<END_OF_HTML) | 
| @@ -3338,7 +3710,9 @@ sub write_source_prolog(*) | 
| <td><br></td> | 
| </tr> | 
| <tr> | 
| -	      <td><pre class="source"> | 
| +	      <td> | 
| +<pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre> | 
| +<pre class="source"> | 
| END_OF_HTML | 
| ; | 
|  | 
| @@ -3347,51 +3721,251 @@ END_OF_HTML | 
|  | 
|  | 
| # | 
| +# get_branch_blocks(brdata) | 
| +# | 
| +# Group branches that belong to the same basic block. | 
| +# | 
| +# Returns: [block1, block2, ...] | 
| +# block:   [branch1, branch2, ...] | 
| +# branch:  [block_num, branch_num, taken_count, text_length, open, close] | 
| +# | 
| + | 
| +sub get_branch_blocks($) | 
| +{ | 
| +	my ($brdata) = @_; | 
| +	my $last_block_num; | 
| +	my $block = []; | 
| +	my @blocks; | 
| +	my $i; | 
| +	my $num = br_ivec_len($brdata); | 
| + | 
| +	# Group branches | 
| +	for ($i = 0; $i < $num; $i++) { | 
| +		my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i); | 
| +		my $br; | 
| + | 
| +		if (defined($last_block_num) && $block_num != $last_block_num) { | 
| +			push(@blocks, $block); | 
| +			$block = []; | 
| +		} | 
| +		$br = [$block_num, $branch, $taken, 3, 0, 0]; | 
| +		push(@{$block}, $br); | 
| +		$last_block_num = $block_num; | 
| +	} | 
| +	push(@blocks, $block) if (scalar(@{$block}) > 0); | 
| + | 
| +	# Add braces to first and last branch in group | 
| +	foreach $block (@blocks) { | 
| +		$block->[0]->[$BR_OPEN] = 1; | 
| +		$block->[0]->[$BR_LEN]++; | 
| +		$block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1; | 
| +		$block->[scalar(@{$block}) - 1]->[$BR_LEN]++; | 
| +	} | 
| + | 
| +	return @blocks; | 
| +} | 
| + | 
| +# | 
| +# get_block_len(block) | 
| +# | 
| +# Calculate total text length of all branches in a block of branches. | 
| +# | 
| + | 
| +sub get_block_len($) | 
| +{ | 
| +	my ($block) = @_; | 
| +	my $len = 0; | 
| +	my $branch; | 
| + | 
| +	foreach $branch (@{$block}) { | 
| +		$len += $branch->[$BR_LEN]; | 
| +	} | 
| + | 
| +	return $len; | 
| +} | 
| + | 
| + | 
| +# | 
| +# get_branch_html(brdata) | 
| +# | 
| +# Return a list of HTML lines which represent the specified branch coverage | 
| +# data in source code view. | 
| +# | 
| + | 
| +sub get_branch_html($) | 
| +{ | 
| +	my ($brdata) = @_; | 
| +	my @blocks = get_branch_blocks($brdata); | 
| +	my $block; | 
| +	my $branch; | 
| +	my $line_len = 0; | 
| +	my $line = [];	# [branch2|" ", branch|" ", ...] | 
| +	my @lines;	# [line1, line2, ...] | 
| +	my @result; | 
| + | 
| +	# Distribute blocks to lines | 
| +	foreach $block (@blocks) { | 
| +		my $block_len = get_block_len($block); | 
| + | 
| +		# Does this block fit into the current line? | 
| +		if ($line_len + $block_len <= $br_field_width) { | 
| +			# Add it | 
| +			$line_len += $block_len; | 
| +			push(@{$line}, @{$block}); | 
| +			next; | 
| +		} elsif ($block_len <= $br_field_width) { | 
| +			# It would fit if the line was empty - add it to new | 
| +			# line | 
| +			push(@lines, $line); | 
| +			$line_len = $block_len; | 
| +			$line = [ @{$block} ]; | 
| +			next; | 
| +		} | 
| +		# Split the block into several lines | 
| +		foreach $branch (@{$block}) { | 
| +			if ($line_len + $branch->[$BR_LEN] >= $br_field_width) { | 
| +				# Start a new line | 
| +				if (($line_len + 1 <= $br_field_width) && | 
| +				    scalar(@{$line}) > 0 && | 
| +				    !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) { | 
| +					# Try to align branch symbols to be in | 
| +					# one # row | 
| +					push(@{$line}, " "); | 
| +				} | 
| +				push(@lines, $line); | 
| +				$line_len = 0; | 
| +				$line = []; | 
| +			} | 
| +			push(@{$line}, $branch); | 
| +			$line_len += $branch->[$BR_LEN]; | 
| +		} | 
| +	} | 
| +	push(@lines, $line); | 
| + | 
| +	# Convert to HTML | 
| +	foreach $line (@lines) { | 
| +		my $current = ""; | 
| +		my $current_len = 0; | 
| + | 
| +		foreach $branch (@$line) { | 
| +			# Skip alignment space | 
| +			if ($branch eq " ") { | 
| +				$current .= " "; | 
| +				$current_len++; | 
| +				next; | 
| +			} | 
| + | 
| +			my ($block_num, $br_num, $taken, $len, $open, $close) = | 
| +			   @{$branch}; | 
| +			my $class; | 
| +			my $title; | 
| +			my $text; | 
| + | 
| +			if ($taken eq '-') { | 
| +				$class	= "branchNoExec"; | 
| +				$text	= " # "; | 
| +				$title	= "Branch $br_num was not executed"; | 
| +			} elsif ($taken == 0) { | 
| +				$class	= "branchNoCov"; | 
| +				$text	= " - "; | 
| +				$title	= "Branch $br_num was not taken"; | 
| +			} else { | 
| +				$class	= "branchCov"; | 
| +				$text	= " + "; | 
| +				$title	= "Branch $br_num was taken $taken ". | 
| +					  "time"; | 
| +				$title .= "s" if ($taken > 1); | 
| +			} | 
| +			$current .= "[" if ($open); | 
| +			$current .= "<span class=\"$class\" title=\"$title\">"; | 
| +			$current .= $text."</span>"; | 
| +			$current .= "]" if ($close); | 
| +			$current_len += $len; | 
| +		} | 
| + | 
| +		# Right-align result text | 
| +		if ($current_len < $br_field_width) { | 
| +			$current = (" "x($br_field_width - $current_len)). | 
| +				   $current; | 
| +		} | 
| +		push(@result, $current); | 
| +	} | 
| + | 
| +	return @result; | 
| +} | 
| + | 
| + | 
| +# | 
| +# format_count(count, width) | 
| +# | 
| +# Return a right-aligned representation of count that fits in width characters. | 
| +# | 
| + | 
| +sub format_count($$) | 
| +{ | 
| +	my ($count, $width) = @_; | 
| +	my $result; | 
| +	my $exp; | 
| + | 
| +	$result = sprintf("%*.0f", $width, $count); | 
| +	while (length($result) > $width) { | 
| +		last if ($count < 10); | 
| +		$exp++; | 
| +		$count = int($count/10); | 
| +		$result = sprintf("%*s", $width, ">$count*10^$exp"); | 
| +	} | 
| +	return $result; | 
| +} | 
| + | 
| +# | 
| # write_source_line(filehandle, line_num, source, hit_count, converted, | 
| -#                   add_anchor) | 
| +#                   brdata, add_anchor) | 
| # | 
| # Write formatted source code line. Return a line in a format as needed | 
| # by gen_png() | 
| # | 
|  | 
| -sub write_source_line(*$$$$$) | 
| +sub write_source_line(*$$$$$$) | 
| { | 
| +	my ($handle, $line, $source, $count, $converted, $brdata, | 
| +	    $add_anchor) = @_; | 
| my $source_format; | 
| -	my $count; | 
| +	my $count_format; | 
| my $result; | 
| my $anchor_start = ""; | 
| my $anchor_end = ""; | 
| +	my $count_field_width = $line_field_width - 1; | 
| +	my @br_html; | 
| +	my $html; | 
|  | 
| -	if (!(defined$_[3])) | 
| -	{ | 
| +	# Get branch HTML data for this line | 
| +	@br_html = get_branch_html($brdata) if ($br_coverage); | 
| + | 
| +	if (!defined($count)) { | 
| $result		= ""; | 
| $source_format	= ""; | 
| -		$count		= " "x15; | 
| +		$count_format	= " "x$count_field_width; | 
| } | 
| -	elsif ($_[3] == 0) | 
| -	{ | 
| -		$result		= $_[3]; | 
| +	elsif ($count == 0) { | 
| +		$result		= $count; | 
| $source_format	= '<span class="lineNoCov">'; | 
| -		$count		= sprintf("%15d", $_[3]); | 
| +		$count_format	= format_count($count, $count_field_width); | 
| } | 
| -	elsif ($_[4] && defined($highlight)) | 
| -	{ | 
| -		$result		= "*".$_[3]; | 
| +	elsif ($converted && defined($highlight)) { | 
| +		$result		= "*".$count; | 
| $source_format	= '<span class="lineDiffCov">'; | 
| -		$count		= sprintf("%15d", $_[3]); | 
| +		$count_format	= format_count($count, $count_field_width); | 
| } | 
| -	else | 
| -	{ | 
| -		$result		= $_[3]; | 
| +	else { | 
| +		$result		= $count; | 
| $source_format	= '<span class="lineCov">'; | 
| -		$count		= sprintf("%15d", $_[3]); | 
| +		$count_format	= format_count($count, $count_field_width); | 
| } | 
| - | 
| -	$result .= ":".$_[2]; | 
| +	$result .= ":".$source; | 
|  | 
| # Write out a line number navigation anchor every $nav_resolution | 
| # lines if necessary | 
| -	if ($_[5]) | 
| +	if ($add_anchor) | 
| { | 
| $anchor_start	= "<a name=\"$_[1]\">"; | 
| $anchor_end	= "</a>"; | 
| @@ -3400,13 +3974,23 @@ sub write_source_line(*$$$$$) | 
|  | 
| # ************************************************************* | 
|  | 
| -	write_html($_[0], | 
| -		   $anchor_start. | 
| -		   '<span class="lineNum">'.sprintf("%8d", $_[1]). | 
| -		   " </span>$source_format$count : ". | 
| -		   escape_html($_[2]).($source_format?"</span>":""). | 
| -		   $anchor_end."\n"); | 
| - | 
| +	$html = $anchor_start; | 
| +	$html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>"; | 
| +	$html .= shift(@br_html).":" if ($br_coverage); | 
| +	$html .= "$source_format$count_format : "; | 
| +	$html .= escape_html($source); | 
| +	$html .= "</span>" if ($source_format); | 
| +	$html .= $anchor_end."\n"; | 
| + | 
| +	write_html($handle, $html); | 
| + | 
| +	if ($br_coverage) { | 
| +		# Add lines for overlong branch information | 
| +		foreach (@br_html) { | 
| +			write_html($handle, "<span class=\"lineNum\">". | 
| +				   "         </span>$_\n"); | 
| +		} | 
| +	} | 
| # ************************************************************* | 
|  | 
| return($result); | 
| @@ -3460,8 +4044,8 @@ sub write_html_epilog(*$;$) | 
|  | 
| write_html($_[0], <<END_OF_HTML) | 
| <table width="100%" border=0 cellspacing=0 cellpadding=0> | 
| -	  <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> | 
| -	  <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr> | 
| +	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> | 
| +	    <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr> | 
| </table> | 
| <br> | 
| END_OF_HTML | 
| @@ -3491,7 +4075,7 @@ sub write_frameset(*$$$) | 
| <html lang="en"> | 
|  | 
| <head> | 
| -	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> | 
| +	  <meta http-equiv="Content-Type" content="text/html; charset=$charset"> | 
| <title>$_[3]</title> | 
| <link rel="stylesheet" type="text/css" href="$_[1]gcov.css"> | 
| </head> | 
| @@ -3554,7 +4138,7 @@ sub write_overview(*$$$$) | 
|  | 
| <head> | 
| <title>$_[3]</title> | 
| -	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> | 
| +	  <meta http-equiv="Content-Type" content="text/html; charset=$charset"> | 
| <link rel="stylesheet" type="text/css" href="$_[1]gcov.css"> | 
| </head> | 
|  | 
| @@ -3601,51 +4185,14 @@ END_OF_HTML | 
| } | 
|  | 
|  | 
| -# rate_to_col(found, hit) | 
| -# | 
| -# Return Lo, Med or Hi, depending on the coverage rate. | 
| -# | 
| - | 
| -sub rate_to_col($$) | 
| +sub max($$) | 
| { | 
| -	my ($found, $hit) = @_; | 
| -	my $rate; | 
| - | 
| -	if ($found == 0) { | 
| -		return "Hi"; | 
| -	} | 
| -	$rate = 100 * $hit / $found; | 
| -	if ($rate < $med_limit) { | 
| -		return "Lo"; | 
| -	} elsif ($rate < $hi_limit) { | 
| -		return "Med"; | 
| -	} | 
| -	return "Hi"; | 
| -} | 
| - | 
| -# format_rate(found, hit) | 
| -# | 
| -# Return formatted percent string for coverage rate. | 
| -# | 
| +	my ($a, $b) = @_; | 
|  | 
| -sub format_rate($$) | 
| -{ | 
| -	return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %"; | 
| +	return $a if ($a > $b); | 
| +	return $b; | 
| } | 
|  | 
| -sub get_legend_code($$$) | 
| -{ | 
| -	my ($text, $med, $hi) = @_; | 
| -	my $result; | 
| - | 
| -	$result = <<EOF; | 
| -	            $text<br> | 
| -	            <span class="coverLegendLo">0% to $med%</span> | 
| -	            <span class="coverLegendMed">$med% to $hi%</span> | 
| -	            <span class="coverLegendHi">$hi% to 100%</span> | 
| -EOF | 
| -	return $result; | 
| -} | 
|  | 
| # | 
| # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found, | 
| @@ -3656,7 +4203,7 @@ EOF | 
| # header, test case description header, function view header) | 
| # | 
|  | 
| -sub write_header(*$$$$$$$$) | 
| +sub write_header(*$$$$$$$$$$) | 
| { | 
| local *HTML_HANDLE = $_[0]; | 
| my $type = $_[1]; | 
| @@ -3666,32 +4213,43 @@ sub write_header(*$$$$$$$$) | 
| my $lines_hit = $_[5]; | 
| my $fn_found = $_[6]; | 
| my $fn_hit = $_[7]; | 
| -	my $sort_type = $_[8]; | 
| +	my $br_found = $_[8]; | 
| +	my $br_hit = $_[9]; | 
| +	my $sort_type = $_[10]; | 
| my $base_dir; | 
| my $view; | 
| my $test; | 
| my $base_name; | 
| +	my $style; | 
| +	my $rate; | 
| +	my @row_left; | 
| +	my @row_right; | 
| +	my $num_rows; | 
| +	my $i; | 
| +	my $esc_trunc_name = escape_html($trunc_name); | 
|  | 
| $base_name = basename($rel_filename); | 
|  | 
| # Prepare text for "current view" field | 
| -	if ($type == 0) | 
| +	if ($type == $HDR_DIR) | 
| { | 
| # Main overview | 
| $base_dir = ""; | 
| $view = $overview_title; | 
| } | 
| -	elsif ($type == 1) | 
| +	elsif ($type == $HDR_FILE) | 
| { | 
| # Directory overview | 
| $base_dir = get_relative_base_path($rel_filename); | 
| $view = "<a href=\"$base_dir"."index.$html_ext\">". | 
| -			"$overview_title</a> - $trunc_name"; | 
| +			"$overview_title</a> - $esc_trunc_name"; | 
| } | 
| -	elsif ($type == 2 || $type == 4) | 
| +	elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC) | 
| { | 
| # File view | 
| my $dir_name = dirname($rel_filename); | 
| +		my $esc_base_name = escape_html($base_name); | 
| +		my $esc_dir_name = escape_html($dir_name); | 
|  | 
| $base_dir = get_relative_base_path($dir_name); | 
| if ($frames) | 
| @@ -3701,26 +4259,28 @@ sub write_header(*$$$$$$$$) | 
| $view = "<a href=\"$base_dir"."index.$html_ext\" ". | 
| "target=\"_parent\">$overview_title</a> - ". | 
| "<a href=\"index.$html_ext\" target=\"_parent\">". | 
| -				"$dir_name</a> - $base_name"; | 
| +				"$esc_dir_name</a> - $esc_base_name"; | 
| } | 
| else | 
| { | 
| $view = "<a href=\"$base_dir"."index.$html_ext\">". | 
| "$overview_title</a> - ". | 
| "<a href=\"index.$html_ext\">". | 
| -				"$dir_name</a> - $base_name"; | 
| +				"$esc_dir_name</a> - $esc_base_name"; | 
| } | 
|  | 
| # Add function suffix | 
| if ($func_coverage) { | 
| -			if ($type == 2) { | 
| +			$view .= "<span style=\"font-size: 80%;\">"; | 
| +			if ($type == $HDR_SOURCE) { | 
| $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)"; | 
| -			} elsif ($type == 4) { | 
| +			} elsif ($type == $HDR_FUNC) { | 
| $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)"; | 
| } | 
| +			$view .= "</span>"; | 
| } | 
| } | 
| -	elsif ($type == 3) | 
| +	elsif ($type == $HDR_TESTDESC) | 
| { | 
| # Test description header | 
| $base_dir = ""; | 
| @@ -3732,84 +4292,126 @@ sub write_header(*$$$$$$$$) | 
| $test = escape_html($test_title); | 
|  | 
| # Append link to test description page if available | 
| -	if (%test_description && ($type != 3)) | 
| +	if (%test_description && ($type != $HDR_TESTDESC)) | 
| { | 
| -		if ($frames && ($type == 2 || $type == 4)) | 
| +		if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) | 
| { | 
| # Need to break frameset when clicking this link | 
| -			$test .= " ( <a href=\"$base_dir". | 
| +			$test .= " ( <span style=\"font-size:80%;\">". | 
| +				 "<a href=\"$base_dir". | 
| "descriptions.$html_ext\" target=\"_parent\">". | 
| -				 "view descriptions</a> )"; | 
| +				 "view descriptions</a></span> )"; | 
| } | 
| else | 
| { | 
| -			$test .= " ( <a href=\"$base_dir". | 
| +			$test .= " ( <span style=\"font-size:80%;\">". | 
| +				 "<a href=\"$base_dir". | 
| "descriptions.$html_ext\">". | 
| -				 "view descriptions</a> )"; | 
| +				 "view descriptions</a></span> )"; | 
| } | 
| } | 
|  | 
| # Write header | 
| write_header_prolog(*HTML_HANDLE, $base_dir); | 
| -	write_header_line(*HTML_HANDLE, 0, "Current view:", $view, | 
| -			  "Found", "Hit", "Coverage"); | 
| -	write_header_line(*HTML_HANDLE, 1, "Test:", $test, "Lines:", | 
| -			  $lines_found, $lines_hit, | 
| -			  $rate_name[classify_rate($lines_found, $lines_hit, | 
| -						   $med_limit, $hi_limit)], | 
| -			  format_rate($lines_found, $lines_hit)); | 
| -	if ($func_coverage) { | 
| -		write_header_line(*HTML_HANDLE, 1, "Date:", $date, "Functions:", | 
| -				  $fn_found, $fn_hit, | 
| -				  $rate_name[classify_rate($fn_found, | 
| -							   $fn_hit, | 
| -							   $fn_med_limit, | 
| -							   $fn_hi_limit)], | 
| -				  format_rate($fn_found, $fn_hit)); | 
| + | 
| +	# Left row | 
| +	push(@row_left, [[ "10%", "headerItem", "Current view:" ], | 
| +			 [ "35%", "headerValue", $view ]]); | 
| +	push(@row_left, [[undef, "headerItem", "Test:"], | 
| +			 [undef, "headerValue", $test]]); | 
| +	push(@row_left, [[undef, "headerItem", "Date:"], | 
| +			 [undef, "headerValue", $date]]); | 
| + | 
| +	# Right row | 
| +	if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) { | 
| +		my $text = <<END_OF_HTML; | 
| +            Lines: | 
| +            <span class="coverLegendCov">hit</span> | 
| +            <span class="coverLegendNoCov">not hit</span> | 
| +END_OF_HTML | 
| +		if ($br_coverage) { | 
| +			$text .= <<END_OF_HTML; | 
| +            | Branches: | 
| +            <span class="coverLegendCov">+</span> taken | 
| +            <span class="coverLegendNoCov">-</span> not taken | 
| +            <span class="coverLegendNoCov">#</span> not executed | 
| +END_OF_HTML | 
| +		} | 
| +		push(@row_left, [[undef, "headerItem", "Legend:"], | 
| +				 [undef, "headerValueLeg", $text]]); | 
| +	} elsif ($legend && ($type != $HDR_TESTDESC)) { | 
| +		my $text = <<END_OF_HTML; | 
| +	    Rating: | 
| +            <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: < $med_limit %</span> | 
| +            <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: >= $med_limit %</span> | 
| +            <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: >= $hi_limit %</span> | 
| +END_OF_HTML | 
| +		push(@row_left, [[undef, "headerItem", "Legend:"], | 
| +				 [undef, "headerValueLeg", $text]]); | 
| +	} | 
| +	if ($type == $HDR_TESTDESC) { | 
| +		push(@row_right, [[ "55%" ]]); | 
| } else { | 
| -		write_header_line(*HTML_HANDLE, 4, "Date:", $date); | 
| -	} | 
| -	if ($legend) { | 
| -		if ($type == 0 || $type == 1) { | 
| -			my $line_code = get_legend_code("Line coverage:", | 
| -						$med_limit, $hi_limit); | 
| -			my $func_code = ""; | 
| - | 
| -			if ($func_coverage) { | 
| -				$func_code = get_legend_code( | 
| -						"Function coverage:", | 
| -						$fn_med_limit, | 
| -						$fn_hi_limit); | 
| -			} | 
| -			write_header_line(*HTML_HANDLE, 2, "Colors:", | 
| -				$line_code, $func_code); | 
| -		} elsif ($type == 2 || $type == 4) { | 
| -			write_header_line(*HTML_HANDLE, 3, "Colors:", | 
| -					  "not hit", "hit"); | 
| +		push(@row_right, [["15%", undef, undef ], | 
| +				  ["10%", "headerCovTableHead", "Hit" ], | 
| +				  ["10%", "headerCovTableHead", "Total" ], | 
| +				  ["15%", "headerCovTableHead", "Coverage"]]); | 
| +	} | 
| +	# Line coverage | 
| +	$style = $rate_name[classify_rate($lines_found, $lines_hit, | 
| +					  $med_limit, $hi_limit)]; | 
| +	$rate = rate($lines_hit, $lines_found, " %"); | 
| +	push(@row_right, [[undef, "headerItem", "Lines:"], | 
| +			  [undef, "headerCovTableEntry", $lines_hit], | 
| +			  [undef, "headerCovTableEntry", $lines_found], | 
| +			  [undef, "headerCovTableEntry$style", $rate]]) | 
| +			if ($type != $HDR_TESTDESC); | 
| +	# Function coverage | 
| +	if ($func_coverage) { | 
| +		$style = $rate_name[classify_rate($fn_found, $fn_hit, | 
| +						  $fn_med_limit, $fn_hi_limit)]; | 
| +		$rate = rate($fn_hit, $fn_found, " %"); | 
| +		push(@row_right, [[undef, "headerItem", "Functions:"], | 
| +				  [undef, "headerCovTableEntry", $fn_hit], | 
| +				  [undef, "headerCovTableEntry", $fn_found], | 
| +				  [undef, "headerCovTableEntry$style", $rate]]) | 
| +			if ($type != $HDR_TESTDESC); | 
| +	} | 
| +	# Branch coverage | 
| +	if ($br_coverage) { | 
| +		$style = $rate_name[classify_rate($br_found, $br_hit, | 
| +						  $br_med_limit, $br_hi_limit)]; | 
| +		$rate = rate($br_hit, $br_found, " %"); | 
| +		push(@row_right, [[undef, "headerItem", "Branches:"], | 
| +				  [undef, "headerCovTableEntry", $br_hit], | 
| +				  [undef, "headerCovTableEntry", $br_found], | 
| +				  [undef, "headerCovTableEntry$style", $rate]]) | 
| +			if ($type != $HDR_TESTDESC); | 
| +	} | 
| + | 
| +	# Print rows | 
| +	$num_rows = max(scalar(@row_left), scalar(@row_right)); | 
| +	for ($i = 0; $i < $num_rows; $i++) { | 
| +		my $left = $row_left[$i]; | 
| +		my $right = $row_right[$i]; | 
| + | 
| +		if (!defined($left)) { | 
| +			$left = [[undef, undef, undef], [undef, undef, undef]]; | 
| +		} | 
| +		if (!defined($right)) { | 
| +			$right = []; | 
| } | 
| +		write_header_line(*HTML_HANDLE, @{$left}, | 
| +				  [ $i == 0 ? "5%" : undef, undef, undef], | 
| +				  @{$right}); | 
| } | 
| + | 
| +	# Fourth line | 
| write_header_epilog(*HTML_HANDLE, $base_dir); | 
| } | 
|  | 
|  | 
| # | 
| -# split_filename(filename) | 
| -# | 
| -# Return (path, filename, extension) for a given FILENAME. | 
| -# | 
| - | 
| -sub split_filename($) | 
| -{ | 
| -	if (!$_[0]) { return(); } | 
| -	my @path_components = split('/', $_[0]); | 
| -	my @file_components = split('\.', pop(@path_components)); | 
| -	my $extension = pop(@file_components); | 
| - | 
| -	return (join("/",@path_components), join(".",@file_components), | 
| -		$extension); | 
| -} | 
| - | 
| -# | 
| # get_sorted_keys(hash_ref, sort_type) | 
| # | 
|  | 
| @@ -3817,15 +4419,18 @@ sub get_sorted_keys($$) | 
| { | 
| my ($hash, $type) = @_; | 
|  | 
| -	if ($type == 0) { | 
| +	if ($type == $SORT_FILE) { | 
| # Sort by name | 
| return sort(keys(%{$hash})); | 
| -	} elsif ($type == 1) { | 
| +	} elsif ($type == $SORT_LINE) { | 
| # Sort by line coverage | 
| -		return sort({$hash->{$a}[5] <=> $hash->{$b}[5]} keys(%{$hash})); | 
| -	} elsif ($type == 2) { | 
| +		return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash})); | 
| +	} elsif ($type == $SORT_FUNC) { | 
| # Sort by function coverage; | 
| -		return sort({$hash->{$a}[6] <=> $hash->{$b}[6]}	keys(%{$hash})); | 
| +		return sort({$hash->{$a}[8] <=> $hash->{$b}[8]}	keys(%{$hash})); | 
| +	} elsif ($type == $SORT_BRANCH) { | 
| +		# Sort by br coverage; | 
| +		return sort({$hash->{$a}[9] <=> $hash->{$b}[9]}	keys(%{$hash})); | 
| } | 
| } | 
|  | 
| @@ -3858,7 +4463,7 @@ sub get_file_code($$$$) | 
| my $link; | 
|  | 
| if ($sort_button) { | 
| -		if ($type == 1) { | 
| +		if ($type == $HEAD_NO_DETAIL) { | 
| $link = "index.$html_ext"; | 
| } else { | 
| $link = "index-detail.$html_ext"; | 
| @@ -3875,12 +4480,12 @@ sub get_line_code($$$$$) | 
| my $result = $text; | 
| my $sort_link; | 
|  | 
| -	if ($type == 1) { | 
| +	if ($type == $HEAD_NO_DETAIL) { | 
| # Just text | 
| if ($sort_button) { | 
| $sort_link = "index-sort-l.$html_ext"; | 
| } | 
| -	} elsif ($type == 2) { | 
| +	} elsif ($type == $HEAD_DETAIL_HIDDEN) { | 
| # Text + link to detail view | 
| $result .= ' ( <a class="detail" href="index-detail'. | 
| $fileview_sortname[$sort_type].'.'.$html_ext. | 
| @@ -3910,7 +4515,7 @@ sub get_func_code($$$$) | 
| my $link; | 
|  | 
| if ($sort_button) { | 
| -		if ($type == 1) { | 
| +		if ($type == $HEAD_NO_DETAIL) { | 
| $link = "index-sort-f.$html_ext"; | 
| } else { | 
| $link = "index-detail-sort-f.$html_ext"; | 
| @@ -3920,9 +4525,26 @@ sub get_func_code($$$$) | 
| return $result; | 
| } | 
|  | 
| +sub get_br_code($$$$) | 
| +{ | 
| +	my ($type, $text, $sort_button, $base) = @_; | 
| +	my $result = $text; | 
| +	my $link; | 
| + | 
| +	if ($sort_button) { | 
| +		if ($type == $HEAD_NO_DETAIL) { | 
| +			$link = "index-sort-b.$html_ext"; | 
| +		} else { | 
| +			$link = "index-detail-sort-b.$html_ext"; | 
| +		} | 
| +	} | 
| +	$result .= get_sort_code($link, "Sort by branch coverage", $base); | 
| +	return $result; | 
| +} | 
| + | 
| # | 
| # write_file_table(filehandle, base_dir, overview, testhash, testfnchash, | 
| -#                  fileview, sort_type) | 
| +#                  testbrhash, fileview, sort_type) | 
| # | 
| # Write a complete file table. OVERVIEW is a reference to a hash containing | 
| # the following mapping: | 
| @@ -3940,70 +4562,107 @@ sub get_func_code($$$$) | 
| # otherwise. | 
| # | 
|  | 
| -sub write_file_table(*$$$$$$) | 
| +sub write_file_table(*$$$$$$$) | 
| { | 
| local *HTML_HANDLE = $_[0]; | 
| my $base_dir = $_[1]; | 
| my $overview = $_[2]; | 
| my $testhash = $_[3]; | 
| my $testfnchash = $_[4]; | 
| -	my $fileview = $_[5]; | 
| -	my $sort_type = $_[6]; | 
| +	my $testbrhash = $_[5]; | 
| +	my $fileview = $_[6]; | 
| +	my $sort_type = $_[7]; | 
| my $filename; | 
| my $bar_graph; | 
| my $hit; | 
| my $found; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
| my $page_link; | 
| my $testname; | 
| my $testdata; | 
| my $testfncdata; | 
| -	my $testcount; | 
| -	my $testfnccount; | 
| +	my $testbrdata; | 
| my %affecting_tests; | 
| my $line_code = ""; | 
| my $func_code; | 
| +	my $br_code; | 
| my $file_code; | 
| +	my @head_columns; | 
|  | 
| # Determine HTML code for column headings | 
| if (($base_dir ne "") && $show_details) | 
| { | 
| my $detailed = keys(%{$testhash}); | 
|  | 
| -		$file_code = get_file_code($detailed ? 2 : 1, | 
| +		$file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN : | 
| +					$HEAD_NO_DETAIL, | 
| $fileview ? "Filename" : "Directory", | 
| -					$sort && $sort_type != 0, $base_dir); | 
| -		$line_code = get_line_code($detailed ? 3 : 2, $sort_type, | 
| +					$sort && $sort_type != $SORT_FILE, | 
| +					$base_dir); | 
| +		$line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN : | 
| +					$HEAD_DETAIL_HIDDEN, | 
| +					$sort_type, | 
| "Line Coverage", | 
| -					$sort && $sort_type != 1, $base_dir); | 
| -		$func_code = get_func_code($detailed ? 2 : 1, "Functions", | 
| -					$sort && $sort_type != 2, $base_dir); | 
| +					$sort && $sort_type != $SORT_LINE, | 
| +					$base_dir); | 
| +		$func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN : | 
| +					$HEAD_NO_DETAIL, | 
| +					"Functions", | 
| +					$sort && $sort_type != $SORT_FUNC, | 
| +					$base_dir); | 
| +		$br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN : | 
| +					$HEAD_NO_DETAIL, | 
| +					"Branches", | 
| +					$sort && $sort_type != $SORT_BRANCH, | 
| +					$base_dir); | 
| } else { | 
| -		$file_code = get_file_code(1, | 
| +		$file_code = get_file_code($HEAD_NO_DETAIL, | 
| $fileview ? "Filename" : "Directory", | 
| -					$sort && $sort_type != 0, $base_dir); | 
| -		$line_code = get_line_code(1, $sort_type, "Line Coverage", | 
| -					$sort && $sort_type != 1, $base_dir); | 
| -		$func_code = get_func_code(1, "Functions", | 
| -					$sort && $sort_type != 2, $base_dir); | 
| -	} | 
| - | 
| -	write_file_table_prolog(*HTML_HANDLE, $file_code, $line_code, | 
| -				$func_code); | 
| +					$sort && $sort_type != $SORT_FILE, | 
| +					$base_dir); | 
| +		$line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage", | 
| +					$sort && $sort_type != $SORT_LINE, | 
| +					$base_dir); | 
| +		$func_code = get_func_code($HEAD_NO_DETAIL, "Functions", | 
| +					$sort && $sort_type != $SORT_FUNC, | 
| +					$base_dir); | 
| +		$br_code = get_br_code($HEAD_NO_DETAIL, "Branches", | 
| +					$sort && $sort_type != $SORT_BRANCH, | 
| +					$base_dir); | 
| +	} | 
| +	push(@head_columns, [ $line_code, 3 ]); | 
| +	push(@head_columns, [ $func_code, 2]) if ($func_coverage); | 
| +	push(@head_columns, [ $br_code, 2]) if ($br_coverage); | 
| + | 
| +	write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns); | 
|  | 
| foreach $filename (get_sorted_keys($overview, $sort_type)) | 
| { | 
| -		($found, $hit, $fn_found, $fn_hit, $page_link) | 
| -			= @{$overview->{$filename}}; | 
| -		$bar_graph = get_bar_graph_code($base_dir, $found, $hit); | 
| +		my @columns; | 
| +		($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit, | 
| +		 $page_link) = @{$overview->{$filename}}; | 
| + | 
| +		# Line coverage | 
| +		push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]); | 
| +		# Function coverage | 
| +		if ($func_coverage) { | 
| +			push(@columns, [$fn_found, $fn_hit, $fn_med_limit, | 
| +					$fn_hi_limit, 0]); | 
| +		} | 
| +		# Branch coverage | 
| +		if ($br_coverage) { | 
| +			push(@columns, [$br_found, $br_hit, $br_med_limit, | 
| +					$br_hi_limit, 0]); | 
| +		} | 
| +		write_file_table_entry(*HTML_HANDLE, $base_dir, $filename, | 
| +				       $page_link, @columns); | 
|  | 
| $testdata = $testhash->{$filename}; | 
| $testfncdata = $testfnchash->{$filename}; | 
| - | 
| -		write_file_table_entry(*HTML_HANDLE, $filename, $bar_graph, | 
| -				       $found, $hit, $fn_found, $fn_hit, | 
| -				       $page_link); | 
| +		$testbrdata = $testbrhash->{$filename}; | 
|  | 
| # Check whether we should write test specific coverage | 
| # as well | 
| @@ -4011,18 +4670,15 @@ sub write_file_table(*$$$$$$) | 
|  | 
| # Filter out those tests that actually affect this file | 
| %affecting_tests = %{ get_affecting_tests($testdata, | 
| -					$testfncdata) }; | 
| +					$testfncdata, $testbrdata) }; | 
|  | 
| # Does any of the tests affect this file at all? | 
| if (!%affecting_tests) { next; } | 
|  | 
| -		# Write test details for this entry | 
| -		write_file_table_detail_heading(*HTML_HANDLE, "Test name", | 
| -						"Lines hit", "Functions hit"); | 
| - | 
| foreach $testname (keys(%affecting_tests)) | 
| { | 
| -			($found, $hit, $fn_found, $fn_hit) = | 
| +			my @results; | 
| +			($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) = | 
| split(",", $affecting_tests{$testname}); | 
|  | 
| # Insert link to description of available | 
| @@ -4033,8 +4689,11 @@ sub write_file_table(*$$$$$$) | 
| "$testname</a>"; | 
| } | 
|  | 
| +			push(@results, [$found, $hit]); | 
| +			push(@results, [$fn_found, $fn_hit]) if ($func_coverage); | 
| +			push(@results, [$br_found, $br_hit]) if ($br_coverage); | 
| write_file_table_detail_entry(*HTML_HANDLE, $testname, | 
| -				$found, $hit, $fn_found, $fn_hit); | 
| +				@results); | 
| } | 
| } | 
|  | 
| @@ -4095,39 +4754,224 @@ sub get_func_found_and_hit($) | 
|  | 
|  | 
| # | 
| -# get_affecting_tests(testdata, testfncdata) | 
| +# br_taken_to_num(taken) | 
| +# | 
| +# Convert a branch taken value .info format to number format. | 
| +# | 
| + | 
| +sub br_taken_to_num($) | 
| +{ | 
| +	my ($taken) = @_; | 
| + | 
| +	return 0 if ($taken eq '-'); | 
| +	return $taken + 1; | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_num_to_taken(taken) | 
| +# | 
| +# Convert a branch taken value in number format to .info format. | 
| +# | 
| + | 
| +sub br_num_to_taken($) | 
| +{ | 
| +	my ($taken) = @_; | 
| + | 
| +	return '-' if ($taken == 0); | 
| +	return $taken - 1; | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_taken_add(taken1, taken2) | 
| +# | 
| +# Return the result of taken1 + taken2 for 'branch taken' values. | 
| +# | 
| + | 
| +sub br_taken_add($$) | 
| +{ | 
| +	my ($t1, $t2) = @_; | 
| + | 
| +	return $t1 if (!defined($t2)); | 
| +	return $t2 if (!defined($t1)); | 
| +	return $t1 if ($t2 eq '-'); | 
| +	return $t2 if ($t1 eq '-'); | 
| +	return $t1 + $t2; | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_taken_sub(taken1, taken2) | 
| +# | 
| +# Return the result of taken1 - taken2 for 'branch taken' values. Return 0 | 
| +# if the result would become negative. | 
| +# | 
| + | 
| +sub br_taken_sub($$) | 
| +{ | 
| +	my ($t1, $t2) = @_; | 
| + | 
| +	return $t1 if (!defined($t2)); | 
| +	return undef if (!defined($t1)); | 
| +	return $t1 if ($t1 eq '-'); | 
| +	return $t1 if ($t2 eq '-'); | 
| +	return 0 if $t2 > $t1; | 
| +	return $t1 - $t2; | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_ivec_len(vector) | 
| +# | 
| +# Return the number of entries in the branch coverage vector. | 
| +# | 
| + | 
| +sub br_ivec_len($) | 
| +{ | 
| +	my ($vec) = @_; | 
| + | 
| +	return 0 if (!defined($vec)); | 
| +	return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_ivec_get(vector, number) | 
| +# | 
| +# Return an entry from the branch coverage vector. | 
| +# | 
| + | 
| +sub br_ivec_get($$) | 
| +{ | 
| +	my ($vec, $num) = @_; | 
| +	my $block; | 
| +	my $branch; | 
| +	my $taken; | 
| +	my $offset = $num * $BR_VEC_ENTRIES; | 
| + | 
| +	# Retrieve data from vector | 
| +	$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 | 
| +	$taken = br_num_to_taken($taken); | 
| + | 
| +	return ($block, $branch, $taken); | 
| +} | 
| + | 
| + | 
| +# | 
| +# br_ivec_push(vector, block, branch, taken) | 
| +# | 
| +# Add an entry to the branch coverage vector. If an entry with the same | 
| +# branch ID already exists, add the corresponding taken values. | 
| +# | 
| + | 
| +sub br_ivec_push($$$$) | 
| +{ | 
| +	my ($vec, $block, $branch, $taken) = @_; | 
| +	my $offset; | 
| +	my $num = br_ivec_len($vec); | 
| +	my $i; | 
| + | 
| +	$vec = "" if (!defined($vec)); | 
| + | 
| +	# Check if branch already exists in vector | 
| +	for ($i = 0; $i < $num; $i++) { | 
| +		my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); | 
| + | 
| +		next if ($v_block != $block || $v_branch != $branch); | 
| + | 
| +		# Add taken counts | 
| +		$taken = br_taken_add($taken, $v_taken); | 
| +		last; | 
| +	} | 
| + | 
| +	$offset = $i * $BR_VEC_ENTRIES; | 
| +	$taken = br_taken_to_num($taken); | 
| + | 
| +	# Add to vector | 
| +	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; | 
| +} | 
| + | 
| + | 
| +# | 
| +# get_br_found_and_hit(sumbrcount) | 
| +# | 
| +# Return (br_found, br_hit) for sumbrcount | 
| +# | 
| + | 
| +sub get_br_found_and_hit($) | 
| +{ | 
| +	my ($sumbrcount) = @_; | 
| +	my $line; | 
| +	my $br_found = 0; | 
| +	my $br_hit = 0; | 
| + | 
| +	foreach $line (keys(%{$sumbrcount})) { | 
| +		my $brdata = $sumbrcount->{$line}; | 
| +		my $i; | 
| +		my $num = br_ivec_len($brdata); | 
| + | 
| +		for ($i = 0; $i < $num; $i++) { | 
| +			my $taken; | 
| + | 
| +			(undef, undef, $taken) = br_ivec_get($brdata, $i); | 
| + | 
| +			$br_found++; | 
| +			$br_hit++ if ($taken ne "-" && $taken > 0); | 
| +		} | 
| +	} | 
| + | 
| +	return ($br_found, $br_hit); | 
| +} | 
| + | 
| + | 
| +# | 
| +# get_affecting_tests(testdata, testfncdata, testbrdata) | 
| # | 
| # HASHREF contains a mapping filename -> (linenumber -> exec count). Return | 
| # a hash containing mapping filename -> "lines found, lines hit" for each | 
| # filename which has a nonzero hit count. | 
| # | 
|  | 
| -sub get_affecting_tests($$) | 
| +sub get_affecting_tests($$$) | 
| { | 
| -	my $testdata = $_[0]; | 
| -	my $testfncdata = $_[1]; | 
| +	my ($testdata, $testfncdata, $testbrdata) = @_; | 
| my $testname; | 
| my $testcount; | 
| my $testfnccount; | 
| +	my $testbrcount; | 
| my %result; | 
| my $found; | 
| my $hit; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
|  | 
| foreach $testname (keys(%{$testdata})) | 
| { | 
| # Get (line number -> count) hash for this test case | 
| $testcount = $testdata->{$testname}; | 
| $testfnccount = $testfncdata->{$testname}; | 
| +		$testbrcount = $testbrdata->{$testname}; | 
|  | 
| # Calculate sum | 
| ($found, $hit) = get_found_and_hit($testcount); | 
| ($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount); | 
| +		($br_found, $br_hit) = get_br_found_and_hit($testbrcount); | 
|  | 
| if ($hit>0) | 
| { | 
| -			$result{$testname} = "$found,$hit,$fn_found,$fn_hit"; | 
| +			$result{$testname} = "$found,$hit,$fn_found,$fn_hit,". | 
| +					     "$br_found,$br_hit"; | 
| } | 
| } | 
|  | 
| @@ -4149,7 +4993,7 @@ sub get_hash_reverse($) | 
|  | 
| # | 
| # write_source(filehandle, source_filename, count_data, checksum_data, | 
| -#              converted_data, func_data) | 
| +#              converted_data, func_data, sumbrcount) | 
| # | 
| # Write an HTML view of a source code file. Returns a list containing | 
| # data as needed by gen_png(). | 
| @@ -4157,7 +5001,7 @@ sub get_hash_reverse($) | 
| # Die on error. | 
| # | 
|  | 
| -sub write_source($$$$$$) | 
| +sub write_source($$$$$$$) | 
| { | 
| local *HTML_HANDLE = $_[0]; | 
| local *SOURCE_HANDLE; | 
| @@ -4168,24 +5012,51 @@ sub write_source($$$$$$) | 
| my $checkdata = $_[3]; | 
| my $converted = $_[4]; | 
| my $funcdata  = $_[5]; | 
| +	my $sumbrcount = $_[6]; | 
| my $datafunc = get_hash_reverse($funcdata); | 
| my $add_anchor; | 
| +	my @file; | 
|  | 
| if ($_[2]) | 
| { | 
| %count_data = %{$_[2]}; | 
| } | 
|  | 
| -	open(SOURCE_HANDLE, "<".$source_filename) | 
| -          # or die("ERROR: cannot open $source_filename for reading!\n"); | 
| -          or open(SOURCE_HANDLE, "</dev/null"); | 
| +	if (!open(SOURCE_HANDLE, "<", $source_filename)) { | 
| +		my @lines; | 
| +		my $last_line = 0; | 
| + | 
| +		if (!$ignore[$ERROR_SOURCE]) { | 
| +			die("ERROR: cannot read $source_filename\n"); | 
| +		} | 
| + | 
| +		# Continue without source file | 
| +		warn("WARNING: cannot read $source_filename!\n"); | 
| + | 
| +		@lines = sort( { $a <=> $b }  keys(%count_data)); | 
| +		if (@lines) { | 
| +			$last_line = $lines[scalar(@lines) - 1]; | 
| +		} | 
| +		return ( ":" ) if ($last_line < 1); | 
| + | 
| +		# Simulate gcov behavior | 
| +		for ($line_number = 1; $line_number <= $last_line; | 
| +		     $line_number++) { | 
| +			push(@file, "/* EOF */"); | 
| +		} | 
| +	} else { | 
| +		@file = <SOURCE_HANDLE>; | 
| +	} | 
|  | 
| write_source_prolog(*HTML_HANDLE); | 
| - | 
| -	for ($line_number = 1; <SOURCE_HANDLE> ; $line_number++) | 
| -	{ | 
| +	$line_number = 0; | 
| +	foreach (@file) { | 
| +		$line_number++; | 
| chomp($_); | 
|  | 
| +		# Also remove CR from line-end | 
| +		s/\015$//; | 
| + | 
| # Source code matches coverage data? | 
| if (defined($checkdata->{$line_number}) && | 
| ($checkdata->{$line_number} ne md5_base64($_))) | 
| @@ -4212,7 +5083,7 @@ sub write_source($$$$$$) | 
| write_source_line(HTML_HANDLE, $line_number, | 
| $_, $count_data{$line_number}, | 
| $converted->{$line_number}, | 
| -					$add_anchor)); | 
| +					$sumbrcount->{$line_number}, $add_anchor)); | 
| } | 
|  | 
| close(SOURCE_HANDLE); | 
| @@ -4271,7 +5142,8 @@ sub funcview_get_sorted($$$) | 
|  | 
| # | 
| # write_function_table(filehandle, source_file, sumcount, funcdata, | 
| -#		       sumfnccount, testfncdata) | 
| +#		       sumfnccount, testfncdata, sumbrcount, testbrdata, | 
| +#		       base_name, base_dir, sort_type) | 
| # | 
| # Write an HTML table listing all functions in a source file, including | 
| # also function call counts and line coverages inside of each function. | 
| @@ -4279,7 +5151,7 @@ sub funcview_get_sorted($$$) | 
| # Die on error. | 
| # | 
|  | 
| -sub write_function_table(*$$$$$$$$) | 
| +sub write_function_table(*$$$$$$$$$$) | 
| { | 
| local *HTML_HANDLE = $_[0]; | 
| my $source = $_[1]; | 
| @@ -4287,9 +5159,11 @@ sub write_function_table(*$$$$$$$$) | 
| my $funcdata = $_[3]; | 
| my $sumfncdata = $_[4]; | 
| my $testfncdata = $_[5]; | 
| -	my $name = $_[6]; | 
| -	my $base = $_[7]; | 
| -	my $type = $_[8]; | 
| +	my $sumbrcount = $_[6]; | 
| +	my $testbrdata = $_[7]; | 
| +	my $name = $_[8]; | 
| +	my $base = $_[9]; | 
| +	my $type = $_[10]; | 
| my $func; | 
| my $func_code; | 
| my $count_code; | 
| @@ -4310,11 +5184,23 @@ END_OF_HTML | 
|  | 
| # Get a sorted table | 
| foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) { | 
| +		if (!defined($funcdata->{$func})) | 
| +		{ | 
| +			next; | 
| +		} | 
| + | 
| my $startline = $funcdata->{$func} - $func_offset; | 
| -		my $name = escape_html($func); | 
| +		my $name = $func; | 
| my $count = $sumfncdata->{$name}; | 
| my $countstyle; | 
|  | 
| +		# Demangle C++ function names if requested | 
| +		if ($demangle_cpp) { | 
| +			$name = `c++filt "$name"`; | 
| +			chomp($name); | 
| +		} | 
| +		# Escape any remaining special characters | 
| +		$name = escape_html($name); | 
| if ($startline < 1) { | 
| $startline = 1; | 
| } | 
| @@ -4403,14 +5289,16 @@ sub subtract_counts($$) | 
|  | 
| sub subtract_fnccounts($$) | 
| { | 
| -	my %data = %{$_[0]}; | 
| -	my %base = %{$_[1]}; | 
| +	my %data; | 
| +	my %base; | 
| my $func; | 
| my $data_count; | 
| my $base_count; | 
| my $fn_hit = 0; | 
| my $fn_found = 0; | 
|  | 
| +	%data = %{$_[0]} if (defined($_[0])); | 
| +	%base = %{$_[1]} if (defined($_[1])); | 
| foreach $func (keys(%data)) { | 
| $fn_found++; | 
| $data_count = $data{$func}; | 
| @@ -4453,18 +5341,24 @@ sub apply_baseline($$) | 
| my $data_funcdata; | 
| my $data_checkdata; | 
| my $data_testfncdata; | 
| +	my $data_testbrdata; | 
| my $data_count; | 
| my $data_testfnccount; | 
| +	my $data_testbrcount; | 
| my $base; | 
| my $base_checkdata; | 
| my $base_sumfnccount; | 
| +	my $base_sumbrcount; | 
| my $base_count; | 
| my $sumcount; | 
| my $sumfnccount; | 
| +	my $sumbrcount; | 
| my $found; | 
| my $hit; | 
| my $fn_found; | 
| my $fn_hit; | 
| +	my $br_found; | 
| +	my $br_hit; | 
|  | 
| foreach $filename (keys(%data_hash)) | 
| { | 
| @@ -4480,9 +5374,11 @@ sub apply_baseline($$) | 
|  | 
| # Get set entries for data and baseline | 
| ($data_testdata, undef, $data_funcdata, $data_checkdata, | 
| -		 $data_testfncdata) = get_info_entry($data); | 
| +		 $data_testfncdata, undef, $data_testbrdata) = | 
| +			get_info_entry($data); | 
| (undef, $base_count, undef, $base_checkdata, undef, | 
| -		 $base_sumfnccount) = get_info_entry($base); | 
| +		 $base_sumfnccount, undef, $base_sumbrcount) = | 
| +			get_info_entry($base); | 
|  | 
| # Check for compatible checksums | 
| merge_checksums($data_checkdata, $base_checkdata, $filename); | 
| @@ -4490,6 +5386,7 @@ sub apply_baseline($$) | 
| # sumcount has to be calculated anew | 
| $sumcount = {}; | 
| $sumfnccount = {}; | 
| +		$sumbrcount = {}; | 
|  | 
| # For each test case, subtract test specific counts | 
| foreach $testname (keys(%{$data_testdata})) | 
| @@ -4497,12 +5394,17 @@ sub apply_baseline($$) | 
| # Get counts of both data and baseline | 
| $data_count = $data_testdata->{$testname}; | 
| $data_testfnccount = $data_testfncdata->{$testname}; | 
| +			$data_testbrcount = $data_testbrdata->{$testname}; | 
|  | 
| ($data_count, undef, $hit) = | 
| subtract_counts($data_count, $base_count); | 
| ($data_testfnccount) = | 
| subtract_fnccounts($data_testfnccount, | 
| $base_sumfnccount); | 
| +			($data_testbrcount) = | 
| +				combine_brcount($data_testbrcount, | 
| +						 $base_sumbrcount, $BR_SUB); | 
| + | 
|  | 
| # Check whether this test case did hit any line at all | 
| if ($hit > 0) | 
| @@ -4511,6 +5413,8 @@ sub apply_baseline($$) | 
| $data_testdata->{$testname} = $data_count; | 
| $data_testfncdata->{$testname} = | 
| $data_testfnccount; | 
| +				$data_testbrdata->{$testname} = | 
| +					$data_testbrcount; | 
| } | 
| else | 
| { | 
| @@ -4518,19 +5422,24 @@ sub apply_baseline($$) | 
| # file | 
| delete($data_testdata->{$testname}); | 
| delete($data_testfncdata->{$testname}); | 
| +				delete($data_testbrdata->{$testname}); | 
| } | 
|  | 
| # Add counts to sum of counts | 
| ($sumcount, $found, $hit) = | 
| add_counts($sumcount, $data_count); | 
| ($sumfnccount, $fn_found, $fn_hit) = | 
| -				add_fnccounts($sumfnccount, $data_testfnccount); | 
| +				add_fnccount($sumfnccount, $data_testfnccount); | 
| +			($sumbrcount, $br_found, $br_hit) = | 
| +				combine_brcount($sumbrcount, $data_testbrcount, | 
| +						$BR_ADD); | 
| } | 
|  | 
| # Write back resulting entry | 
| set_info_entry($data, $data_testdata, $sumcount, $data_funcdata, | 
| $data_checkdata, $data_testfncdata, $sumfnccount, | 
| -			       $found, $hit, $fn_found, $fn_hit); | 
| +			       $data_testbrdata, $sumbrcount, $found, $hit, | 
| +			       $fn_found, $fn_hit, $br_found, $br_hit); | 
|  | 
| $data_hash{$filename} = $data; | 
| } | 
| @@ -4627,12 +5536,12 @@ sub system_no_output($@) | 
| local *OLD_STDOUT; | 
|  | 
| # Save old stdout and stderr handles | 
| -	($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); | 
| -	($mode & 2) && open(OLD_STDERR, ">>&STDERR"); | 
| +	($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); | 
| +	($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); | 
|  | 
| # Redirect to /dev/null | 
| -	($mode & 1) && open(STDOUT, ">/dev/null"); | 
| -	($mode & 2) && open(STDERR, ">/dev/null"); | 
| +	($mode & 1) && open(STDOUT, ">", "/dev/null"); | 
| +	($mode & 2) && open(STDERR, ">", "/dev/null"); | 
|  | 
| system(@_); | 
| $result = $?; | 
| @@ -4642,8 +5551,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; | 
| } | 
| @@ -4664,7 +5573,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; | 
| @@ -4703,8 +5612,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($) | 
| @@ -4713,8 +5622,9 @@ sub apply_config($) | 
|  | 
| foreach (keys(%{$ref})) | 
| { | 
| -		if (defined($config->{$_})) | 
| -		{ | 
| +		if (defined($opt_rc{$_})) { | 
| +			${$ref->{$_}} = $opt_rc{$_}; | 
| +		} elsif (defined($config->{$_})) { | 
| ${$ref->{$_}} = $config->{$_}; | 
| } | 
| } | 
| @@ -4737,7 +5647,7 @@ sub get_html_prolog($) | 
| { | 
| local *HANDLE; | 
|  | 
| -		open(HANDLE, "<".$filename) | 
| +		open(HANDLE, "<", $filename) | 
| or die("ERROR: cannot open html prolog $filename!\n"); | 
| while (<HANDLE>) | 
| { | 
| @@ -4753,7 +5663,7 @@ sub get_html_prolog($) | 
| <html lang="en"> | 
|  | 
| <head> | 
| -  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> | 
| +  <meta http-equiv="Content-Type" content="text/html; charset=$charset"> | 
| <title>\@pagetitle\@</title> | 
| <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css"> | 
| </head> | 
| @@ -4783,7 +5693,7 @@ sub get_html_epilog($) | 
| { | 
| local *HANDLE; | 
|  | 
| -		open(HANDLE, "<".$filename) | 
| +		open(HANDLE, "<", $filename) | 
| or die("ERROR: cannot open html epilog $filename!\n"); | 
| while (<HANDLE>) | 
| { | 
| @@ -4818,3 +5728,72 @@ sub die_handler($) | 
|  | 
| die("$tool_name: $msg"); | 
| } | 
| + | 
| +# | 
| +# parse_ignore_errors(@ignore_errors) | 
| +# | 
| +# Parse user input about which errors to ignore. | 
| +# | 
| + | 
| +sub parse_ignore_errors(@) | 
| +{ | 
| +	my (@ignore_errors) = @_; | 
| +	my @items; | 
| +	my $item; | 
| + | 
| +	return if (!@ignore_errors); | 
| + | 
| +	foreach $item (@ignore_errors) { | 
| +		$item =~ s/\s//g; | 
| +		if ($item =~ /,/) { | 
| +			# Split and add comma-separated parameters | 
| +			push(@items, split(/,/, $item)); | 
| +		} else { | 
| +			# Add single parameter | 
| +			push(@items, $item); | 
| +		} | 
| +	} | 
| +	foreach $item (@items) { | 
| +		my $item_id = $ERROR_ID{lc($item)}; | 
| + | 
| +		if (!defined($item_id)) { | 
| +			die("ERROR: unknown argument for --ignore-errors: ". | 
| +			    "$item\n"); | 
| +		} | 
| +		$ignore[$item_id] = 1; | 
| +	} | 
| +} | 
| + | 
| +# | 
| +# rate(hit, found[, suffix, precision, width]) | 
| +# | 
| +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only | 
| +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. | 
| +# PRECISION specifies the precision of the result. SUFFIX defines a | 
| +# string that is appended to the result if FOUND is non-zero. Spaces | 
| +# are added to the start of the resulting string until it is at least WIDTH | 
| +# characters wide. | 
| +# | 
| + | 
| +sub rate($$;$$$) | 
| +{ | 
| +        my ($hit, $found, $suffix, $precision, $width) = @_; | 
| +        my $rate; | 
| + | 
| +	# Assign defaults if necessary | 
| +        $precision	= 1	if (!defined($precision)); | 
| +	$suffix		= ""	if (!defined($suffix)); | 
| +	$width		= 0	if (!defined($width)); | 
| + | 
| +        return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); | 
| +        $rate = sprintf("%.*f", $precision, $hit * 100 / $found); | 
| + | 
| +	# Adjust rates if necessary | 
| +        if ($rate == 0 && $hit > 0) { | 
| +		$rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); | 
| +        } elsif ($rate == 100 && $hit != $found) { | 
| +		$rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); | 
| +	} | 
| + | 
| +	return sprintf("%*s", $width, $rate.$suffix); | 
| +} | 
|  |