Home | History | Annotate | Download | only in bin
      1 #!/usr/bin/perl -w
      2 #
      3 #   Copyright (c) International Business Machines  Corp., 2002,2010
      4 #
      5 #   This program is free software;  you can redistribute it and/or modify
      6 #   it under the terms of the GNU General Public License as published by
      7 #   the Free Software Foundation; either version 2 of the License, or (at
      8 #   your option) any later version.
      9 #
     10 #   This program is distributed in the hope that it will be useful, but
     11 #   WITHOUT ANY WARRANTY;  without even the implied warranty of
     12 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13 #   General Public License for more details. 
     14 #
     15 #   You should have received a copy of the GNU General Public License
     16 #   along with this program;  if not, write to the Free Software
     17 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     18 #
     19 #
     20 # genhtml
     21 #
     22 #   This script generates HTML output from .info files as created by the
     23 #   geninfo script. Call it with --help and refer to the genhtml man page
     24 #   to get information on usage and available options.
     25 #
     26 #
     27 # History:
     28 #   2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter (at] de.ibm.com>
     29 #                         IBM Lab Boeblingen
     30 #        based on code by Manoj Iyer <manjo (at] mail.utexas.edu> and
     31 #                         Megan Bock <mbock (at] us.ibm.com>
     32 #                         IBM Austin
     33 #   2002-08-27 / Peter Oberparleiter: implemented frame view
     34 #   2002-08-29 / Peter Oberparleiter: implemented test description filtering
     35 #                so that by default only descriptions for test cases which
     36 #                actually hit some source lines are kept
     37 #   2002-09-05 / Peter Oberparleiter: implemented --no-sourceview
     38 #   2002-09-05 / Mike Kobler: One of my source file paths includes a "+" in
     39 #                the directory name.  I found that genhtml.pl died when it
     40 #                encountered it. I was able to fix the problem by modifying
     41 #                the string with the escape character before parsing it.
     42 #   2002-10-26 / Peter Oberparleiter: implemented --num-spaces
     43 #   2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error
     44 #                when trying to combine .info files containing data without
     45 #                a test name
     46 #   2003-04-10 / Peter Oberparleiter: extended fix by Mike to also cover
     47 #                other special characters
     48 #   2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT
     49 #   2003-07-10 / Peter Oberparleiter: added line checksum support
     50 #   2004-08-09 / Peter Oberparleiter: added configuration file support
     51 #   2005-03-04 / Cal Pierog: added legend to HTML output, fixed coloring of
     52 #                "good coverage" background
     53 #   2006-03-18 / Marcus Boerger: added --custom-intro, --custom-outro and
     54 #                overwrite --no-prefix if --prefix is present
     55 #   2006-03-20 / Peter Oberparleiter: changes to custom_* function (rename
     56 #                to html_prolog/_epilog, minor modifications to implementation),
     57 #                changed prefix/noprefix handling to be consistent with current
     58 #                logic
     59 #   2006-03-20 / Peter Oberparleiter: added --html-extension option
     60 #   2008-07-14 / Tom Zoerner: added --function-coverage command line option;
     61 #                added function table to source file page
     62 #   2008-08-13 / Peter Oberparleiter: modified function coverage
     63 #                implementation (now enabled per default),
     64 #                introduced sorting option (enabled per default)
     65 #
     66 
     67 use strict;
     68 use File::Basename; 
     69 use Getopt::Long;
     70 use Digest::MD5 qw(md5_base64);
     71 
     72 
     73 # Global constants
     74 our $title		= "LCOV - code coverage report";
     75 our $lcov_version	= 'LCOV version 1.9';
     76 our $lcov_url		= "http://ltp.sourceforge.net/coverage/lcov.php";
     77 our $tool_name		= basename($0);
     78 
     79 # Specify coverage rate limits (in %) for classifying file entries
     80 # HI:   $hi_limit <= rate <= 100          graph color: green
     81 # MED: $med_limit <= rate <  $hi_limit    graph color: orange
     82 # LO:          0  <= rate <  $med_limit   graph color: red
     83 
     84 # For line coverage/all coverage types if not specified
     85 our $hi_limit = 90;
     86 our $med_limit = 75;
     87 
     88 # For function coverage
     89 our $fn_hi_limit;
     90 our $fn_med_limit;
     91 
     92 # For branch coverage
     93 our $br_hi_limit;
     94 our $br_med_limit;
     95 
     96 # Width of overview image
     97 our $overview_width = 80;
     98 
     99 # Resolution of overview navigation: this number specifies the maximum
    100 # difference in lines between the position a user selected from the overview
    101 # and the position the source code window is scrolled to.
    102 our $nav_resolution = 4;
    103 
    104 # Clicking a line in the overview image should show the source code view at
    105 # a position a bit further up so that the requested line is not the first
    106 # line in the window. This number specifies that offset in lines.
    107 our $nav_offset = 10;
    108 
    109 # Clicking on a function name should show the source code at a position a
    110 # few lines before the first line of code of that function. This number
    111 # specifies that offset in lines.
    112 our $func_offset = 2;
    113 
    114 our $overview_title = "top level";
    115 
    116 # Width for line coverage information in the source code view
    117 our $line_field_width = 12;
    118 
    119 # Width for branch coverage information in the source code view
    120 our $br_field_width = 16;
    121 
    122 # Internal Constants
    123 
    124 # Header types
    125 our $HDR_DIR		= 0;
    126 our $HDR_FILE		= 1;
    127 our $HDR_SOURCE		= 2;
    128 our $HDR_TESTDESC	= 3;
    129 our $HDR_FUNC		= 4;
    130 
    131 # Sort types
    132 our $SORT_FILE		= 0;
    133 our $SORT_LINE		= 1;
    134 our $SORT_FUNC		= 2;
    135 our $SORT_BRANCH	= 3;
    136 
    137 # Fileview heading types
    138 our $HEAD_NO_DETAIL	= 1;
    139 our $HEAD_DETAIL_HIDDEN	= 2;
    140 our $HEAD_DETAIL_SHOWN	= 3;
    141 
    142 # Offsets for storing branch coverage data in vectors
    143 our $BR_BLOCK		= 0;
    144 our $BR_BRANCH		= 1;
    145 our $BR_TAKEN		= 2;
    146 our $BR_VEC_ENTRIES	= 3;
    147 our $BR_VEC_WIDTH	= 32;
    148 
    149 # Additional offsets used when converting branch coverage data to HTML
    150 our $BR_LEN	= 3;
    151 our $BR_OPEN	= 4;
    152 our $BR_CLOSE	= 5;
    153 
    154 # Branch data combination types
    155 our $BR_SUB = 0;
    156 our $BR_ADD = 1;
    157 
    158 # Data related prototypes
    159 sub print_usage(*);
    160 sub gen_html();
    161 sub html_create($$);
    162 sub process_dir($);
    163 sub process_file($$$);
    164 sub info(@);
    165 sub read_info_file($);
    166 sub get_info_entry($);
    167 sub set_info_entry($$$$$$$$$;$$$$$$);
    168 sub get_prefix(@);
    169 sub shorten_prefix($);
    170 sub get_dir_list(@);
    171 sub get_relative_base_path($);
    172 sub read_testfile($);
    173 sub get_date_string();
    174 sub create_sub_dir($);
    175 sub subtract_counts($$);
    176 sub add_counts($$);
    177 sub apply_baseline($$);
    178 sub remove_unused_descriptions();
    179 sub get_found_and_hit($);
    180 sub get_affecting_tests($$$);
    181 sub combine_info_files($$);
    182 sub merge_checksums($$$);
    183 sub combine_info_entries($$$);
    184 sub apply_prefix($$);
    185 sub system_no_output($@);
    186 sub read_config($);
    187 sub apply_config($);
    188 sub get_html_prolog($);
    189 sub get_html_epilog($);
    190 sub write_dir_page($$$$$$$$$$$$$$$$$);
    191 sub classify_rate($$$$);
    192 sub br_taken_add($$);
    193 sub br_taken_sub($$);
    194 sub br_ivec_len($);
    195 sub br_ivec_get($$);
    196 sub br_ivec_push($$$$);
    197 sub combine_brcount($$$);
    198 sub get_br_found_and_hit($);
    199 sub warn_handler($);
    200 sub die_handler($);
    201 
    202 
    203 # HTML related prototypes
    204 sub escape_html($);
    205 sub get_bar_graph_code($$$);
    206 
    207 sub write_png_files();
    208 sub write_htaccess_file();
    209 sub write_css_file();
    210 sub write_description_file($$$$$$$);
    211 sub write_function_table(*$$$$$$$$$$);
    212 
    213 sub write_html(*$);
    214 sub write_html_prolog(*$$);
    215 sub write_html_epilog(*$;$);
    216 
    217 sub write_header(*$$$$$$$$$$);
    218 sub write_header_prolog(*$);
    219 sub write_header_line(*@);
    220 sub write_header_epilog(*$);
    221 
    222 sub write_file_table(*$$$$$$$);
    223 sub write_file_table_prolog(*$@);
    224 sub write_file_table_entry(*$$$@);
    225 sub write_file_table_detail_entry(*$@);
    226 sub write_file_table_epilog(*);
    227 
    228 sub write_test_table_prolog(*$);
    229 sub write_test_table_entry(*$$);
    230 sub write_test_table_epilog(*);
    231 
    232 sub write_source($$$$$$$);
    233 sub write_source_prolog(*);
    234 sub write_source_line(*$$$$$$);
    235 sub write_source_epilog(*);
    236 
    237 sub write_frameset(*$$$);
    238 sub write_overview_line(*$$$);
    239 sub write_overview(*$$$$);
    240 
    241 # External prototype (defined in genpng)
    242 sub gen_png($$$@);
    243 
    244 
    245 # Global variables & initialization
    246 our %info_data;		# Hash containing all data from .info file
    247 our $dir_prefix;	# Prefix to remove from all sub directories
    248 our %test_description;	# Hash containing test descriptions if available
    249 our $date = get_date_string();
    250 
    251 our @info_filenames;	# List of .info files to use as data source
    252 our $test_title;	# Title for output as written to each page header
    253 our $output_directory;	# Name of directory in which to store output
    254 our $base_filename;	# Optional name of file containing baseline data
    255 our $desc_filename;	# Name of file containing test descriptions
    256 our $css_filename;	# Optional name of external stylesheet file to use
    257 our $quiet;		# If set, suppress information messages
    258 our $help;		# Help option flag
    259 our $version;		# Version option flag
    260 our $show_details;	# If set, generate detailed directory view
    261 our $no_prefix;		# If set, do not remove filename prefix
    262 our $func_coverage = 1;	# If set, generate function coverage statistics
    263 our $no_func_coverage;	# Disable func_coverage
    264 our $br_coverage = 1;	# If set, generate branch coverage statistics
    265 our $no_br_coverage;	# Disable br_coverage
    266 our $sort = 1;		# If set, provide directory listings with sorted entries
    267 our $no_sort;		# Disable sort
    268 our $frames;		# If set, use frames for source code view
    269 our $keep_descriptions;	# If set, do not remove unused test case descriptions
    270 our $no_sourceview;	# If set, do not create a source code view for each file
    271 our $highlight;		# If set, highlight lines covered by converted data only
    272 our $legend;		# If set, include legend in output
    273 our $tab_size = 8;	# Number of spaces to use in place of tab
    274 our $config;		# Configuration file contents
    275 our $html_prolog_file;	# Custom HTML prolog file (up to and including <body>)
    276 our $html_epilog_file;	# Custom HTML epilog file (from </body> onwards)
    277 our $html_prolog;	# Actual HTML prolog
    278 our $html_epilog;	# Actual HTML epilog
    279 our $html_ext = "html";	# Extension for generated HTML files
    280 our $html_gzip = 0;	# Compress with gzip
    281 our $demangle_cpp = 0;	# Demangle C++ function names
    282 our @fileview_sortlist;
    283 our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b");
    284 our @funcview_sortlist;
    285 our @rate_name = ("Lo", "Med", "Hi");
    286 our @rate_png = ("ruby.png", "amber.png", "emerald.png");
    287 
    288 our $cwd = `pwd`;	# Current working directory
    289 chomp($cwd);
    290 our $tool_dir = dirname($0);	# Directory where genhtml tool is installed
    291 
    292 
    293 #
    294 # Code entry point
    295 #
    296 
    297 $SIG{__WARN__} = \&warn_handler;
    298 $SIG{__DIE__} = \&die_handler;
    299 
    300 # Prettify version string
    301 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
    302 
    303 # Add current working directory if $tool_dir is not already an absolute path
    304 if (! ($tool_dir =~ /^\/(.*)$/))
    305 {
    306 	$tool_dir = "$cwd/$tool_dir";
    307 }
    308 
    309 # Read configuration file if available
    310 if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
    311 {
    312 	$config = read_config($ENV{"HOME"}."/.lcovrc");
    313 }
    314 elsif (-r "/etc/lcovrc")
    315 {
    316 	$config = read_config("/etc/lcovrc");
    317 }
    318 
    319 if ($config)
    320 {
    321 	# Copy configuration file values to variables
    322 	apply_config({
    323 		"genhtml_css_file"		=> \$css_filename,
    324 		"genhtml_hi_limit"		=> \$hi_limit,
    325 		"genhtml_med_limit"		=> \$med_limit,
    326 		"genhtml_line_field_width"	=> \$line_field_width,
    327 		"genhtml_overview_width"	=> \$overview_width,
    328 		"genhtml_nav_resolution"	=> \$nav_resolution,
    329 		"genhtml_nav_offset"		=> \$nav_offset,
    330 		"genhtml_keep_descriptions"	=> \$keep_descriptions,
    331 		"genhtml_no_prefix"		=> \$no_prefix,
    332 		"genhtml_no_source"		=> \$no_sourceview,
    333 		"genhtml_num_spaces"		=> \$tab_size,
    334 		"genhtml_highlight"		=> \$highlight,
    335 		"genhtml_legend"		=> \$legend,
    336 		"genhtml_html_prolog"		=> \$html_prolog_file,
    337 		"genhtml_html_epilog"		=> \$html_epilog_file,
    338 		"genhtml_html_extension"	=> \$html_ext,
    339 		"genhtml_html_gzip"		=> \$html_gzip,
    340 		"genhtml_function_hi_limit"	=> \$fn_hi_limit,
    341 		"genhtml_function_med_limit"	=> \$fn_med_limit,
    342 		"genhtml_function_coverage"	=> \$func_coverage,
    343 		"genhtml_branch_hi_limit"	=> \$br_hi_limit,
    344 		"genhtml_branch_med_limit"	=> \$br_med_limit,
    345 		"genhtml_branch_coverage"	=> \$br_coverage,
    346 		"genhtml_branch_field_width"	=> \$br_field_width,
    347 		"genhtml_sort"			=> \$sort,
    348 		});
    349 }
    350 
    351 # Copy limit values if not specified
    352 $fn_hi_limit	= $hi_limit if (!defined($fn_hi_limit));
    353 $fn_med_limit	= $med_limit if (!defined($fn_med_limit));
    354 $br_hi_limit	= $hi_limit if (!defined($br_hi_limit));
    355 $br_med_limit	= $med_limit if (!defined($br_med_limit));
    356 
    357 # Parse command line options
    358 if (!GetOptions("output-directory|o=s"	=> \$output_directory,
    359 		"title|t=s"		=> \$test_title,
    360 		"description-file|d=s"	=> \$desc_filename,
    361 		"keep-descriptions|k"	=> \$keep_descriptions,
    362 		"css-file|c=s"		=> \$css_filename,
    363 		"baseline-file|b=s"	=> \$base_filename,
    364 		"prefix|p=s"		=> \$dir_prefix,
    365 		"num-spaces=i"		=> \$tab_size,
    366 		"no-prefix"		=> \$no_prefix,
    367 		"no-sourceview"		=> \$no_sourceview,
    368 		"show-details|s"	=> \$show_details,
    369 		"frames|f"		=> \$frames,
    370 		"highlight"		=> \$highlight,
    371 		"legend"		=> \$legend,
    372 		"quiet|q"		=> \$quiet,
    373 		"help|h|?"		=> \$help,
    374 		"version|v"		=> \$version,
    375 		"html-prolog=s"		=> \$html_prolog_file,
    376 		"html-epilog=s"		=> \$html_epilog_file,
    377 		"html-extension=s"	=> \$html_ext,
    378 		"html-gzip"		=> \$html_gzip,
    379 		"function-coverage"	=> \$func_coverage,
    380 		"no-function-coverage"	=> \$no_func_coverage,
    381 		"branch-coverage"	=> \$br_coverage,
    382 		"no-branch-coverage"	=> \$no_br_coverage,
    383 		"sort"			=> \$sort,
    384 		"no-sort"		=> \$no_sort,
    385 		"demangle-cpp"		=> \$demangle_cpp,
    386 		))
    387 {
    388 	print(STDERR "Use $tool_name --help to get usage information\n");
    389 	exit(1);
    390 } else {
    391 	# Merge options
    392 	if ($no_func_coverage) {
    393 		$func_coverage = 0;
    394 	}
    395 	if ($no_br_coverage) {
    396 		$br_coverage = 0;
    397 	}
    398 
    399 	# Merge sort options
    400 	if ($no_sort) {
    401 		$sort = 0;
    402 	}
    403 }
    404 
    405 @info_filenames = @ARGV;
    406 
    407 # Check for help option
    408 if ($help)
    409 {
    410 	print_usage(*STDOUT);
    411 	exit(0);
    412 }
    413 
    414 # Check for version option
    415 if ($version)
    416 {
    417 	print("$tool_name: $lcov_version\n");
    418 	exit(0);
    419 }
    420 
    421 # Check for info filename
    422 if (!@info_filenames)
    423 {
    424 	die("No filename specified\n".
    425 	    "Use $tool_name --help to get usage information\n");
    426 }
    427 
    428 # Generate a title if none is specified
    429 if (!$test_title)
    430 {
    431 	if (scalar(@info_filenames) == 1)
    432 	{
    433 		# Only one filename specified, use it as title
    434 		$test_title = basename($info_filenames[0]);
    435 	}
    436 	else
    437 	{
    438 		# More than one filename specified, used default title
    439 		$test_title = "unnamed";
    440 	}
    441 }
    442 
    443 # Make sure css_filename is an absolute path (in case we're changing
    444 # directories)
    445 if ($css_filename)
    446 {
    447 	if (!($css_filename =~ /^\/(.*)$/))
    448 	{
    449 		$css_filename = $cwd."/".$css_filename;
    450 	}
    451 }
    452 
    453 # Make sure tab_size is within valid range
    454 if ($tab_size < 1)
    455 {
    456 	print(STDERR "ERROR: invalid number of spaces specified: ".
    457 		     "$tab_size!\n");
    458 	exit(1);
    459 }
    460 
    461 # Get HTML prolog and epilog
    462 $html_prolog = get_html_prolog($html_prolog_file);
    463 $html_epilog = get_html_epilog($html_epilog_file);
    464 
    465 # Issue a warning if --no-sourceview is enabled together with --frames
    466 if ($no_sourceview && defined($frames))
    467 {
    468 	warn("WARNING: option --frames disabled because --no-sourceview ".
    469 	     "was specified!\n");
    470 	$frames = undef;
    471 }
    472 
    473 # Issue a warning if --no-prefix is enabled together with --prefix
    474 if ($no_prefix && defined($dir_prefix))
    475 {
    476 	warn("WARNING: option --prefix disabled because --no-prefix was ".
    477 	     "specified!\n");
    478 	$dir_prefix = undef;
    479 }
    480 
    481 @fileview_sortlist = ($SORT_FILE);
    482 @funcview_sortlist = ($SORT_FILE);
    483 
    484 if ($sort) {
    485 	push(@fileview_sortlist, $SORT_LINE);
    486 	push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage);
    487 	push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage);
    488 	push(@funcview_sortlist, $SORT_LINE);
    489 }
    490 
    491 if ($frames)
    492 {
    493 	# Include genpng code needed for overview image generation
    494 	do("$tool_dir/genpng");
    495 }
    496 
    497 # Ensure that the c++filt tool is available when using --demangle-cpp
    498 if ($demangle_cpp)
    499 {
    500 	if (system_no_output(3, "c++filt", "--version")) {
    501 		die("ERROR: could not find c++filt tool needed for ".
    502 		    "--demangle-cpp\n");
    503 	}
    504 }
    505 
    506 # Make sure output_directory exists, create it if necessary
    507 if ($output_directory)
    508 {
    509 	stat($output_directory);
    510 
    511 	if (! -e _)
    512 	{
    513 		create_sub_dir($output_directory);
    514 	}
    515 }
    516 
    517 # Do something
    518 gen_html();
    519 
    520 exit(0);
    521 
    522 
    523 
    524 #
    525 # print_usage(handle)
    526 #
    527 # Print usage information.
    528 #
    529 
    530 sub print_usage(*)
    531 {
    532 	local *HANDLE = $_[0];
    533 
    534 	print(HANDLE <<END_OF_USAGE);
    535 Usage: $tool_name [OPTIONS] INFOFILE(S)
    536 
    537 Create HTML output for coverage data found in INFOFILE. Note that INFOFILE
    538 may also be a list of filenames.
    539 
    540 Misc:
    541   -h, --help                        Print this help, then exit
    542   -v, --version                     Print version number, then exit
    543   -q, --quiet                       Do not print progress messages
    544 
    545 Operation:
    546   -o, --output-directory OUTDIR     Write HTML output to OUTDIR
    547   -s, --show-details                Generate detailed directory view
    548   -d, --description-file DESCFILE   Read test case descriptions from DESCFILE
    549   -k, --keep-descriptions           Do not remove unused test descriptions
    550   -b, --baseline-file BASEFILE      Use BASEFILE as baseline file
    551   -p, --prefix PREFIX               Remove PREFIX from all directory names
    552       --no-prefix                   Do not remove prefix from directory names
    553       --(no-)function-coverage      Enable (disable) function coverage display
    554       --(no-)branch-coverage        Enable (disable) branch coverage display
    555 
    556 HTML output:
    557   -f, --frames                      Use HTML frames for source code view
    558   -t, --title TITLE                 Display TITLE in header of all pages
    559   -c, --css-file CSSFILE            Use external style sheet file CSSFILE
    560       --no-source                   Do not create source code view
    561       --num-spaces NUM              Replace tabs with NUM spaces in source view
    562       --highlight                   Highlight lines with converted-only data
    563       --legend                      Include color legend in HTML output
    564       --html-prolog FILE            Use FILE as HTML prolog for generated pages
    565       --html-epilog FILE            Use FILE as HTML epilog for generated pages
    566       --html-extension EXT          Use EXT as filename extension for pages
    567       --html-gzip                   Use gzip to compress HTML
    568       --(no-)sort                   Enable (disable) sorted coverage views
    569       --demangle-cpp                Demangle C++ function names
    570 
    571 For more information see: $lcov_url
    572 END_OF_USAGE
    573 	;
    574 }
    575 
    576 
    577 #
    578 # get_rate(found, hit)
    579 #
    580 # Return a relative value for the specified found&hit values
    581 # which is used for sorting the corresponding entries in a
    582 # file list.
    583 #
    584 
    585 sub get_rate($$)
    586 {
    587 	my ($found, $hit) = @_;
    588 
    589 	if ($found == 0) {
    590 		return 10000;
    591 	}
    592 	return int($hit * 1000 / $found) * 10 + 2 - (1 / $found);
    593 }
    594 
    595 
    596 #
    597 # get_overall_line(found, hit, name_singular, name_plural)
    598 #
    599 # Return a string containing overall information for the specified
    600 # found/hit data.
    601 #
    602 
    603 sub get_overall_line($$$$)
    604 {
    605 	my ($found, $hit, $name_sn, $name_pl) = @_;
    606 	my $name;
    607 
    608 	return "no data found" if (!defined($found) || $found == 0);
    609 	$name = ($found == 1) ? $name_sn : $name_pl;
    610 	return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit,
    611 		       $found, $name);
    612 }
    613 
    614 
    615 #
    616 # print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do
    617 #                    br_found, br_hit)
    618 #
    619 # Print overall coverage rates for the specified coverage types.
    620 #
    621 
    622 sub print_overall_rate($$$$$$$$$)
    623 {
    624 	my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit,
    625 	    $br_do, $br_found, $br_hit) = @_;
    626 
    627 	info("Overall coverage rate:\n");
    628 	info("  lines......: %s\n",
    629 	     get_overall_line($ln_found, $ln_hit, "line", "lines"))
    630 		if ($ln_do);
    631 	info("  functions..: %s\n",
    632 	     get_overall_line($fn_found, $fn_hit, "function", "functions"))
    633 		if ($fn_do);
    634 	info("  branches...: %s\n",
    635 	     get_overall_line($br_found, $br_hit, "branch", "branches"))
    636 		if ($br_do);
    637 }
    638 
    639 
    640 #
    641 # gen_html()
    642 #
    643 # Generate a set of HTML pages from contents of .info file INFO_FILENAME.
    644 # Files will be written to the current directory. If provided, test case
    645 # descriptions will be read from .tests file TEST_FILENAME and included
    646 # in ouput.
    647 #
    648 # Die on error.
    649 #
    650 
    651 sub gen_html()
    652 {
    653 	local *HTML_HANDLE;
    654 	my %overview;
    655 	my %base_data;
    656 	my $lines_found;
    657 	my $lines_hit;
    658 	my $fn_found;
    659 	my $fn_hit;
    660 	my $br_found;
    661 	my $br_hit;
    662 	my $overall_found = 0;
    663 	my $overall_hit = 0;
    664 	my $total_fn_found = 0;
    665 	my $total_fn_hit = 0;
    666 	my $total_br_found = 0;
    667 	my $total_br_hit = 0;
    668 	my $dir_name;
    669 	my $link_name;
    670 	my @dir_list;
    671 	my %new_info;
    672 
    673 	# Read in all specified .info files
    674 	foreach (@info_filenames)
    675 	{
    676 		%new_info = %{read_info_file($_)};
    677 
    678 		# Combine %new_info with %info_data
    679 		%info_data = %{combine_info_files(\%info_data, \%new_info)};
    680 	}
    681 
    682 	info("Found %d entries.\n", scalar(keys(%info_data)));
    683 
    684 	# Read and apply baseline data if specified
    685 	if ($base_filename)
    686 	{
    687 		# Read baseline file
    688 		info("Reading baseline file $base_filename\n");
    689 		%base_data = %{read_info_file($base_filename)};
    690 		info("Found %d entries.\n", scalar(keys(%base_data)));
    691 
    692 		# Apply baseline
    693 		info("Subtracting baseline data.\n");
    694 		%info_data = %{apply_baseline(\%info_data, \%base_data)};
    695 	}
    696 
    697 	@dir_list = get_dir_list(keys(%info_data));
    698 
    699 	if ($no_prefix)
    700 	{
    701 		# User requested that we leave filenames alone
    702 		info("User asked not to remove filename prefix\n");
    703 	}
    704 	elsif (!defined($dir_prefix))
    705 	{
    706 		# Get prefix common to most directories in list
    707 		$dir_prefix = get_prefix(@dir_list);
    708 
    709 		if ($dir_prefix)
    710 		{
    711 			info("Found common filename prefix \"$dir_prefix\"\n");
    712 		}
    713 		else
    714 		{
    715 			info("No common filename prefix found!\n");
    716 			$no_prefix=1;
    717 		}
    718 	}
    719 	else
    720 	{
    721 		info("Using user-specified filename prefix \"".
    722 		     "$dir_prefix\"\n");
    723 	}
    724 
    725 	# Read in test description file if specified
    726 	if ($desc_filename)
    727 	{
    728 		info("Reading test description file $desc_filename\n");
    729 		%test_description = %{read_testfile($desc_filename)};
    730 
    731 		# Remove test descriptions which are not referenced
    732 		# from %info_data if user didn't tell us otherwise
    733 		if (!$keep_descriptions)
    734 		{
    735 			remove_unused_descriptions();
    736 		}
    737 	}
    738 
    739 	# Change to output directory if specified
    740 	if ($output_directory)
    741 	{
    742 		chdir($output_directory)
    743 			or die("ERROR: cannot change to directory ".
    744 			"$output_directory!\n");
    745 	}
    746 
    747 	info("Writing .css and .png files.\n");
    748 	write_css_file();
    749 	write_png_files();
    750 
    751 	if ($html_gzip)
    752 	{
    753 		info("Writing .htaccess file.\n");
    754 		write_htaccess_file();
    755 	}
    756 
    757 	info("Generating output.\n");
    758 
    759 	# Process each subdirectory and collect overview information
    760 	foreach $dir_name (@dir_list)
    761 	{
    762 		($lines_found, $lines_hit, $fn_found, $fn_hit,
    763 		 $br_found, $br_hit)
    764 			= process_dir($dir_name);
    765 
    766 		# Remove prefix if applicable
    767 		if (!$no_prefix && $dir_prefix)
    768 		{
    769 			# Match directory names beginning with $dir_prefix
    770 			$dir_name = apply_prefix($dir_name, $dir_prefix);
    771 		}
    772 
    773 		# Generate name for directory overview HTML page
    774 		if ($dir_name =~ /^\/(.*)$/)
    775 		{
    776 			$link_name = substr($dir_name, 1)."/index.$html_ext";
    777 		}
    778 		else
    779 		{
    780 			$link_name = $dir_name."/index.$html_ext";
    781 		}
    782 
    783 		$overview{$dir_name} = [$lines_found, $lines_hit, $fn_found,
    784 					$fn_hit, $br_found, $br_hit, $link_name,
    785 					get_rate($lines_found, $lines_hit),
    786 					get_rate($fn_found, $fn_hit),
    787 					get_rate($br_found, $br_hit)];
    788 		$overall_found	+= $lines_found;
    789 		$overall_hit	+= $lines_hit;
    790 		$total_fn_found	+= $fn_found;
    791 		$total_fn_hit	+= $fn_hit;
    792 		$total_br_found	+= $br_found;
    793 		$total_br_hit	+= $br_hit;
    794 	}
    795 
    796 	# Generate overview page
    797 	info("Writing directory view page.\n");
    798 
    799 	# Create sorted pages
    800 	foreach (@fileview_sortlist) {
    801 		write_dir_page($fileview_sortname[$_], ".", "", $test_title,
    802 			       undef, $overall_found, $overall_hit,
    803 			       $total_fn_found, $total_fn_hit, $total_br_found,
    804 			       $total_br_hit, \%overview, {}, {}, {}, 0, $_);
    805 	}
    806 
    807 	# Check if there are any test case descriptions to write out
    808 	if (%test_description)
    809 	{
    810 		info("Writing test case description file.\n");
    811 		write_description_file( \%test_description,
    812 					$overall_found, $overall_hit,
    813 					$total_fn_found, $total_fn_hit,
    814 					$total_br_found, $total_br_hit);
    815 	}
    816 
    817 	print_overall_rate(1, $overall_found, $overall_hit,
    818 			   $func_coverage, $total_fn_found, $total_fn_hit,
    819 			   $br_coverage, $total_br_found, $total_br_hit);
    820 
    821 	chdir($cwd);
    822 }
    823 
    824 #
    825 # html_create(handle, filename)
    826 #
    827 
    828 sub html_create($$)
    829 {
    830 	my $handle = $_[0];
    831 	my $filename = $_[1];
    832 
    833 	if ($html_gzip)
    834 	{
    835 		open($handle, "|gzip -c >$filename")
    836 			or die("ERROR: cannot open $filename for writing ".
    837 			       "(gzip)!\n");
    838 	}
    839 	else
    840 	{
    841 		open($handle, ">$filename")
    842 			or die("ERROR: cannot open $filename for writing!\n");
    843 	}
    844 }
    845 
    846 sub write_dir_page($$$$$$$$$$$$$$$$$)
    847 {
    848 	my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found,
    849 	    $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found,
    850 	    $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash,
    851 	    $view_type, $sort_type) = @_;
    852 
    853 	# Generate directory overview page including details
    854 	html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext");
    855 	if (!defined($trunc_dir)) {
    856 		$trunc_dir = "";
    857 	}
    858 	write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");
    859 	write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,
    860 		     $overall_found, $overall_hit, $total_fn_found,
    861 		     $total_fn_hit, $total_br_found, $total_br_hit, $sort_type);
    862 	write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash,
    863 			 $testfnchash, $testbrhash, $view_type, $sort_type);
    864 	write_html_epilog(*HTML_HANDLE, $base_dir);
    865 	close(*HTML_HANDLE);
    866 }
    867 
    868 
    869 #
    870 # process_dir(dir_name)
    871 #
    872 
    873 sub process_dir($)
    874 {
    875 	my $abs_dir = $_[0];
    876 	my $trunc_dir;
    877 	my $rel_dir = $abs_dir;
    878 	my $base_dir;
    879 	my $filename;
    880 	my %overview;
    881 	my $lines_found;
    882 	my $lines_hit;
    883 	my $fn_found;
    884 	my $fn_hit;
    885 	my $br_found;
    886 	my $br_hit;
    887 	my $overall_found=0;
    888 	my $overall_hit=0;
    889 	my $total_fn_found=0;
    890 	my $total_fn_hit=0;
    891 	my $total_br_found = 0;
    892 	my $total_br_hit = 0;
    893 	my $base_name;
    894 	my $extension;
    895 	my $testdata;
    896 	my %testhash;
    897 	my $testfncdata;
    898 	my %testfnchash;
    899 	my $testbrdata;
    900 	my %testbrhash;
    901 	my @sort_list;
    902 	local *HTML_HANDLE;
    903 
    904 	# Remove prefix if applicable
    905 	if (!$no_prefix)
    906 	{
    907 		# Match directory name beginning with $dir_prefix
    908 		$rel_dir = apply_prefix($rel_dir, $dir_prefix);
    909 	}
    910 
    911 	$trunc_dir = $rel_dir;
    912 
    913 	# Remove leading /
    914 	if ($rel_dir =~ /^\/(.*)$/)
    915 	{
    916 		$rel_dir = substr($rel_dir, 1);
    917 	}
    918 
    919 	$base_dir = get_relative_base_path($rel_dir);
    920 
    921 	create_sub_dir($rel_dir);
    922 
    923 	# Match filenames which specify files in this directory, not including
    924 	# sub-directories
    925 	foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
    926 	{
    927 		my $page_link;
    928 		my $func_link;
    929 
    930 		($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
    931 		 $br_hit, $testdata, $testfncdata, $testbrdata) =
    932 			process_file($trunc_dir, $rel_dir, $filename);
    933 
    934 		$base_name = basename($filename);
    935 
    936 		if ($no_sourceview) {
    937 			$page_link = "";
    938 		} elsif ($frames) {
    939 			# Link to frameset page
    940 			$page_link = "$base_name.gcov.frameset.$html_ext";
    941 		} else {
    942 			# Link directory to source code view page
    943 			$page_link = "$base_name.gcov.$html_ext";
    944 		}
    945 		$overview{$base_name} = [$lines_found, $lines_hit, $fn_found,
    946 					 $fn_hit, $br_found, $br_hit,
    947 					 $page_link,
    948 					 get_rate($lines_found, $lines_hit),
    949 					 get_rate($fn_found, $fn_hit),
    950 					 get_rate($br_found, $br_hit)];
    951 
    952 		$testhash{$base_name} = $testdata;
    953 		$testfnchash{$base_name} = $testfncdata;
    954 		$testbrhash{$base_name} = $testbrdata;
    955 
    956 		$overall_found	+= $lines_found;
    957 		$overall_hit	+= $lines_hit;
    958 
    959 		$total_fn_found += $fn_found;
    960 		$total_fn_hit   += $fn_hit;
    961 
    962 		$total_br_found += $br_found;
    963 		$total_br_hit   += $br_hit;
    964 	}
    965 
    966 	# Create sorted pages
    967 	foreach (@fileview_sortlist) {
    968 		# Generate directory overview page (without details)	
    969 		write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir,
    970 			       $test_title, $trunc_dir, $overall_found,
    971 			       $overall_hit, $total_fn_found, $total_fn_hit,
    972 			       $total_br_found, $total_br_hit, \%overview, {},
    973 			       {}, {}, 1, $_);
    974 		if (!$show_details) {
    975 			next;
    976 		}
    977 		# Generate directory overview page including details
    978 		write_dir_page("-detail".$fileview_sortname[$_], $rel_dir,
    979 			       $base_dir, $test_title, $trunc_dir,
    980 			       $overall_found, $overall_hit, $total_fn_found,
    981 			       $total_fn_hit, $total_br_found, $total_br_hit,
    982 			       \%overview, \%testhash, \%testfnchash,
    983 			       \%testbrhash, 1, $_);
    984 	}
    985 
    986 	# Calculate resulting line counts
    987 	return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit,
    988 		$total_br_found, $total_br_hit);
    989 }
    990 
    991 
    992 #
    993 # get_converted_lines(testdata)
    994 #
    995 # Return hash of line numbers of those lines which were only covered in
    996 # converted data sets.
    997 #
    998 
    999 sub get_converted_lines($)
   1000 {
   1001 	my $testdata = $_[0];
   1002 	my $testcount;
   1003 	my %converted;
   1004 	my %nonconverted;
   1005 	my $hash;
   1006 	my $testcase;
   1007 	my $line;
   1008 	my %result;
   1009 
   1010 
   1011 	# Get a hash containing line numbers with positive counts both for
   1012 	# converted and original data sets
   1013 	foreach $testcase (keys(%{$testdata}))
   1014 	{
   1015 		# Check to see if this is a converted data set
   1016 		if ($testcase =~ /,diff$/)
   1017 		{
   1018 			$hash = \%converted;
   1019 		}
   1020 		else
   1021 		{
   1022 			$hash = \%nonconverted;
   1023 		}
   1024 
   1025 		$testcount = $testdata->{$testcase};
   1026 		# Add lines with a positive count to hash
   1027 		foreach $line (keys%{$testcount})
   1028 		{
   1029 			if ($testcount->{$line} > 0)
   1030 			{
   1031 				$hash->{$line} = 1;
   1032 			}
   1033 		}
   1034 	}
   1035 
   1036 	# Combine both hashes to resulting list
   1037 	foreach $line (keys(%converted))
   1038 	{
   1039 		if (!defined($nonconverted{$line}))
   1040 		{
   1041 			$result{$line} = 1;
   1042 		}
   1043 	}
   1044 
   1045 	return \%result;
   1046 }
   1047 
   1048 
   1049 sub write_function_page($$$$$$$$$$$$$$$$$$)
   1050 {
   1051 	my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title,
   1052 	    $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit,
   1053 	    $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount,
   1054 	    $testbrdata, $sort_type) = @_;
   1055 	my $pagetitle;
   1056 	my $filename;
   1057 
   1058 	# Generate function table for this file
   1059 	if ($sort_type == 0) {
   1060 		$filename = "$rel_dir/$base_name.func.$html_ext";
   1061 	} else {
   1062 		$filename = "$rel_dir/$base_name.func-sort-c.$html_ext";
   1063 	}
   1064 	html_create(*HTML_HANDLE, $filename);
   1065 	$pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions";
   1066 	write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
   1067 	write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name",
   1068 		     "$rel_dir/$base_name", $lines_found, $lines_hit,
   1069 		     $fn_found, $fn_hit, $br_found, $br_hit, $sort_type);
   1070 	write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext",
   1071 			     $sumcount, $funcdata,
   1072 			     $sumfnccount, $testfncdata, $sumbrcount,
   1073 			     $testbrdata, $base_name,
   1074 			     $base_dir, $sort_type);
   1075 	write_html_epilog(*HTML_HANDLE, $base_dir, 1);
   1076 	close(*HTML_HANDLE);
   1077 }
   1078 
   1079 
   1080 #
   1081 # process_file(trunc_dir, rel_dir, filename)
   1082 #
   1083 
   1084 sub process_file($$$)
   1085 {
   1086 	info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n");
   1087 
   1088 	my $trunc_dir = $_[0];
   1089 	my $rel_dir = $_[1];
   1090 	my $filename = $_[2];
   1091 	my $base_name = basename($filename);
   1092 	my $base_dir = get_relative_base_path($rel_dir);
   1093 	my $testdata;
   1094 	my $testcount;
   1095 	my $sumcount;
   1096 	my $funcdata;
   1097 	my $checkdata;
   1098 	my $testfncdata;
   1099 	my $sumfnccount;
   1100 	my $testbrdata;
   1101 	my $sumbrcount;
   1102 	my $lines_found;
   1103 	my $lines_hit;
   1104 	my $fn_found;
   1105 	my $fn_hit;
   1106 	my $br_found;
   1107 	my $br_hit;
   1108 	my $converted;
   1109 	my @source;
   1110 	my $pagetitle;
   1111 	local *HTML_HANDLE;
   1112 
   1113 	($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
   1114 	 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit,
   1115 	 $fn_found, $fn_hit, $br_found, $br_hit)
   1116 		= get_info_entry($info_data{$filename});
   1117 
   1118 	# Return after this point in case user asked us not to generate
   1119 	# source code view
   1120 	if ($no_sourceview)
   1121 	{
   1122 		return ($lines_found, $lines_hit, $fn_found, $fn_hit,
   1123 			$br_found, $br_hit, $testdata, $testfncdata,
   1124 			$testbrdata);
   1125 	}
   1126 
   1127 	$converted = get_converted_lines($testdata);
   1128 	# Generate source code view for this file
   1129 	html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext");
   1130 	$pagetitle = "LCOV - $test_title - $trunc_dir/$base_name";
   1131 	write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
   1132 	write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name",
   1133 		     "$rel_dir/$base_name", $lines_found, $lines_hit,
   1134 		     $fn_found, $fn_hit, $br_found, $br_hit, 0);
   1135 	@source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata,
   1136 			       $converted, $funcdata, $sumbrcount);
   1137 
   1138 	write_html_epilog(*HTML_HANDLE, $base_dir, 1);
   1139 	close(*HTML_HANDLE);
   1140 
   1141 	if ($func_coverage) {
   1142 		# Create function tables
   1143 		foreach (@funcview_sortlist) {
   1144 			write_function_page($base_dir, $rel_dir, $trunc_dir,
   1145 					    $base_name, $test_title,
   1146 					    $lines_found, $lines_hit,
   1147 					    $fn_found, $fn_hit, $br_found,
   1148 					    $br_hit, $sumcount,
   1149 					    $funcdata, $sumfnccount,
   1150 					    $testfncdata, $sumbrcount,
   1151 					    $testbrdata, $_);
   1152 		}
   1153 	}
   1154 
   1155 	# Additional files are needed in case of frame output
   1156 	if (!$frames)
   1157 	{
   1158 		return ($lines_found, $lines_hit, $fn_found, $fn_hit,
   1159 			$br_found, $br_hit, $testdata, $testfncdata,
   1160 			$testbrdata);
   1161 	}
   1162 
   1163 	# Create overview png file
   1164 	gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
   1165 		@source);
   1166 
   1167 	# Create frameset page
   1168 	html_create(*HTML_HANDLE,
   1169 		    "$rel_dir/$base_name.gcov.frameset.$html_ext");
   1170 	write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle);
   1171 	close(*HTML_HANDLE);
   1172 
   1173 	# Write overview frame
   1174 	html_create(*HTML_HANDLE,
   1175 		    "$rel_dir/$base_name.gcov.overview.$html_ext");
   1176 	write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle,
   1177 		       scalar(@source));
   1178 	close(*HTML_HANDLE);
   1179 
   1180 	return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
   1181 		$br_hit, $testdata, $testfncdata, $testbrdata);
   1182 }
   1183 
   1184 
   1185 #
   1186 # read_info_file(info_filename)
   1187 #
   1188 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
   1189 # be returned as a reference to a hash containing the following mappings:
   1190 #
   1191 # %result: for each filename found in file -> \%data
   1192 #
   1193 # %data: "test"  -> \%testdata
   1194 #        "sum"   -> \%sumcount
   1195 #        "func"  -> \%funcdata
   1196 #        "found" -> $lines_found (number of instrumented lines found in file)
   1197 #	 "hit"   -> $lines_hit (number of executed lines in file)
   1198 #        "check" -> \%checkdata
   1199 #        "testfnc" -> \%testfncdata
   1200 #        "sumfnc"  -> \%sumfnccount
   1201 #        "testbr"  -> \%testbrdata
   1202 #        "sumbr"   -> \%sumbrcount
   1203 #
   1204 # %testdata   : name of test affecting this file -> \%testcount
   1205 # %testfncdata: name of test affecting this file -> \%testfnccount
   1206 # %testbrdata:  name of test affecting this file -> \%testbrcount
   1207 #
   1208 # %testcount   : line number   -> execution count for a single test
   1209 # %testfnccount: function name -> execution count for a single test
   1210 # %testbrcount : line number   -> branch coverage data for a single test
   1211 # %sumcount    : line number   -> execution count for all tests
   1212 # %sumfnccount : function name -> execution count for all tests
   1213 # %sumbrcount  : line number   -> branch coverage data for all tests
   1214 # %funcdata    : function name -> line number
   1215 # %checkdata   : line number   -> checksum of source code line
   1216 # $brdata      : vector of items: block, branch, taken
   1217 # 
   1218 # Note that .info file sections referring to the same file and test name
   1219 # will automatically be combined by adding all execution counts.
   1220 #
   1221 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
   1222 # is compressed using GZIP. If available, GUNZIP will be used to decompress
   1223 # this file.
   1224 #
   1225 # Die on error.
   1226 #
   1227 
   1228 sub read_info_file($)
   1229 {
   1230 	my $tracefile = $_[0];		# Name of tracefile
   1231 	my %result;			# Resulting hash: file -> data
   1232 	my $data;			# Data handle for current entry
   1233 	my $testdata;			#       "             "
   1234 	my $testcount;			#       "             "
   1235 	my $sumcount;			#       "             "
   1236 	my $funcdata;			#       "             "
   1237 	my $checkdata;			#       "             "
   1238 	my $testfncdata;
   1239 	my $testfnccount;
   1240 	my $sumfnccount;
   1241 	my $testbrdata;
   1242 	my $testbrcount;
   1243 	my $sumbrcount;
   1244 	my $line;			# Current line read from .info file
   1245 	my $testname;			# Current test name
   1246 	my $filename;			# Current filename
   1247 	my $hitcount;			# Count for lines hit
   1248 	my $count;			# Execution count of current line
   1249 	my $negative;			# If set, warn about negative counts
   1250 	my $changed_testname;		# If set, warn about changed testname
   1251 	my $line_checksum;		# Checksum of current line
   1252 	my $br_found;
   1253 	my $br_hit;
   1254 	local *INFO_HANDLE;		# Filehandle for .info file
   1255 
   1256 	info("Reading data file $tracefile\n");
   1257 
   1258 	# Check if file exists and is readable
   1259 	stat($_[0]);
   1260 	if (!(-r _))
   1261 	{
   1262 		die("ERROR: cannot read file $_[0]!\n");
   1263 	}
   1264 
   1265 	# Check if this is really a plain file
   1266 	if (!(-f _))
   1267 	{
   1268 		die("ERROR: not a plain file: $_[0]!\n");
   1269 	}
   1270 
   1271 	# Check for .gz extension
   1272 	if ($_[0] =~ /\.gz$/)
   1273 	{
   1274 		# Check for availability of GZIP tool
   1275 		system_no_output(1, "gunzip" ,"-h")
   1276 			and die("ERROR: gunzip command not available!\n");
   1277 
   1278 		# Check integrity of compressed file
   1279 		system_no_output(1, "gunzip", "-t", $_[0])
   1280 			and die("ERROR: integrity check failed for ".
   1281 				"compressed file $_[0]!\n");
   1282 
   1283 		# Open compressed file
   1284 		open(INFO_HANDLE, "gunzip -c $_[0]|")
   1285 			or die("ERROR: cannot start gunzip to decompress ".
   1286 			       "file $_[0]!\n");
   1287 	}
   1288 	else
   1289 	{
   1290 		# Open decompressed file
   1291 		open(INFO_HANDLE, $_[0])
   1292 			or die("ERROR: cannot read file $_[0]!\n");
   1293 	}
   1294 
   1295 	$testname = "";
   1296 	while (<INFO_HANDLE>)
   1297 	{
   1298 		chomp($_);
   1299 		$line = $_;
   1300 
   1301 		# Switch statement
   1302 		foreach ($line)
   1303 		{
   1304 			/^TN:([^,]*)(,diff)?/ && do
   1305 			{
   1306 				# Test name information found
   1307 				$testname = defined($1) ? $1 : "";
   1308 				if ($testname =~ s/\W/_/g)
   1309 				{
   1310 					$changed_testname = 1;
   1311 				}
   1312 				$testname .= $2 if (defined($2));
   1313 				last;
   1314 			};
   1315 
   1316 			/^[SK]F:(.*)/ && do
   1317 			{
   1318 				# Filename information found
   1319 				# Retrieve data for new entry
   1320 				$filename = $1;
   1321 
   1322 				$data = $result{$filename};
   1323 				($testdata, $sumcount, $funcdata, $checkdata,
   1324 				 $testfncdata, $sumfnccount, $testbrdata,
   1325 				 $sumbrcount) =
   1326 					get_info_entry($data);
   1327 
   1328 				if (defined($testname))
   1329 				{
   1330 					$testcount = $testdata->{$testname};
   1331 					$testfnccount = $testfncdata->{$testname};
   1332 					$testbrcount = $testbrdata->{$testname};
   1333 				}
   1334 				else
   1335 				{
   1336 					$testcount = {};
   1337 					$testfnccount = {};
   1338 					$testbrcount = {};
   1339 				}
   1340 				last;
   1341 			};
   1342 
   1343 			/^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
   1344 			{
   1345 				# Fix negative counts
   1346 				$count = $2 < 0 ? 0 : $2;
   1347 				if ($2 < 0)
   1348 				{
   1349 					$negative = 1;
   1350 				}
   1351 				# Execution count found, add to structure
   1352 				# Add summary counts
   1353 				$sumcount->{$1} += $count;
   1354 
   1355 				# Add test-specific counts
   1356 				if (defined($testname))
   1357 				{
   1358 					$testcount->{$1} += $count;
   1359 				}
   1360 
   1361 				# Store line checksum if available
   1362 				if (defined($3))
   1363 				{
   1364 					$line_checksum = substr($3, 1);
   1365 
   1366 					# Does it match a previous definition
   1367 					if (defined($checkdata->{$1}) &&
   1368 					    ($checkdata->{$1} ne
   1369 					     $line_checksum))
   1370 					{
   1371 						die("ERROR: checksum mismatch ".
   1372 						    "at $filename:$1\n");
   1373 					}
   1374 
   1375 					$checkdata->{$1} = $line_checksum;
   1376 				}
   1377 				last;
   1378 			};
   1379 
   1380 			/^FN:(\d+),([^,]+)/ && do
   1381 			{
   1382 				# Function data found, add to structure
   1383 				$funcdata->{$2} = $1;
   1384 
   1385 				# Also initialize function call data
   1386 				if (!defined($sumfnccount->{$2})) {
   1387 					$sumfnccount->{$2} = 0;
   1388 				}
   1389 				if (defined($testname))
   1390 				{
   1391 					if (!defined($testfnccount->{$2})) {
   1392 						$testfnccount->{$2} = 0;
   1393 					}
   1394 				}
   1395 				last;
   1396 			};
   1397 
   1398 			/^FNDA:(\d+),([^,]+)/ && do
   1399 			{
   1400 				# Function call count found, add to structure
   1401 				# Add summary counts
   1402 				$sumfnccount->{$2} += $1;
   1403 
   1404 				# Add test-specific counts
   1405 				if (defined($testname))
   1406 				{
   1407 					$testfnccount->{$2} += $1;
   1408 				}
   1409 				last;
   1410 			};
   1411 
   1412 			/^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
   1413 				# Branch coverage data found
   1414 				my ($line, $block, $branch, $taken) =
   1415 				   ($1, $2, $3, $4);
   1416 
   1417 				$sumbrcount->{$line} =
   1418 					br_ivec_push($sumbrcount->{$line},
   1419 						     $block, $branch, $taken);
   1420 
   1421 				# Add test-specific counts
   1422 				if (defined($testname)) {
   1423 					$testbrcount->{$line} =
   1424 						br_ivec_push(
   1425 							$testbrcount->{$line},
   1426 							$block, $branch,
   1427 							$taken);
   1428 				}
   1429 				last;
   1430 			};
   1431 
   1432 			/^end_of_record/ && do
   1433 			{
   1434 				# Found end of section marker
   1435 				if ($filename)
   1436 				{
   1437 					# Store current section data
   1438 					if (defined($testname))
   1439 					{
   1440 						$testdata->{$testname} =
   1441 							$testcount;
   1442 						$testfncdata->{$testname} =
   1443 							$testfnccount;
   1444 						$testbrdata->{$testname} =
   1445 							$testbrcount;
   1446 					}	
   1447 
   1448 					set_info_entry($data, $testdata,
   1449 						       $sumcount, $funcdata,
   1450 						       $checkdata, $testfncdata,
   1451 						       $sumfnccount,
   1452 						       $testbrdata,
   1453 						       $sumbrcount);
   1454 					$result{$filename} = $data;
   1455 					last;
   1456 				}
   1457 			};
   1458 
   1459 			# default
   1460 			last;
   1461 		}
   1462 	}
   1463 	close(INFO_HANDLE);
   1464 
   1465 	# Calculate lines_found and lines_hit for each file
   1466 	foreach $filename (keys(%result))
   1467 	{
   1468 		$data = $result{$filename};
   1469 
   1470 		($testdata, $sumcount, undef, undef, $testfncdata,
   1471 		 $sumfnccount, $testbrdata, $sumbrcount) =
   1472 			get_info_entry($data);
   1473 
   1474 		# Filter out empty files
   1475 		if (scalar(keys(%{$sumcount})) == 0)
   1476 		{
   1477 			delete($result{$filename});
   1478 			next;
   1479 		}
   1480 		# Filter out empty test cases
   1481 		foreach $testname (keys(%{$testdata}))
   1482 		{
   1483 			if (!defined($testdata->{$testname}) ||
   1484 			    scalar(keys(%{$testdata->{$testname}})) == 0)
   1485 			{
   1486 				delete($testdata->{$testname});
   1487 				delete($testfncdata->{$testname});
   1488 			}
   1489 		}
   1490 
   1491 		$data->{"found"} = scalar(keys(%{$sumcount}));
   1492 		$hitcount = 0;
   1493 
   1494 		foreach (keys(%{$sumcount}))
   1495 		{
   1496 			if ($sumcount->{$_} > 0) { $hitcount++; }
   1497 		}
   1498 
   1499 		$data->{"hit"} = $hitcount;
   1500 
   1501 		# Get found/hit values for function call data
   1502 		$data->{"f_found"} = scalar(keys(%{$sumfnccount}));
   1503 		$hitcount = 0;
   1504 
   1505 		foreach (keys(%{$sumfnccount})) {
   1506 			if ($sumfnccount->{$_} > 0) {
   1507 				$hitcount++;
   1508 			}
   1509 		}
   1510 		$data->{"f_hit"} = $hitcount;
   1511 
   1512 		# Get found/hit values for branch data
   1513 		($br_found, $br_hit) = get_br_found_and_hit($sumbrcount);
   1514 
   1515 		$data->{"b_found"} = $br_found;
   1516 		$data->{"b_hit"} = $br_hit;
   1517 	}
   1518 
   1519 	if (scalar(keys(%result)) == 0)
   1520 	{
   1521 		die("ERROR: no valid records found in tracefile $tracefile\n");
   1522 	}
   1523 	if ($negative)
   1524 	{
   1525 		warn("WARNING: negative counts found in tracefile ".
   1526 		     "$tracefile\n");
   1527 	}
   1528 	if ($changed_testname)
   1529 	{
   1530 		warn("WARNING: invalid characters removed from testname in ".
   1531 		     "tracefile $tracefile\n");
   1532 	}
   1533 
   1534 	return(\%result);
   1535 }
   1536 
   1537 
   1538 #
   1539 # get_info_entry(hash_ref)
   1540 #
   1541 # Retrieve data from an entry of the structure generated by read_info_file().
   1542 # Return a list of references to hashes:
   1543 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
   1544 #  ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
   1545 #  functions found, functions hit)
   1546 #
   1547 
   1548 sub get_info_entry($)
   1549 {
   1550 	my $testdata_ref = $_[0]->{"test"};
   1551 	my $sumcount_ref = $_[0]->{"sum"};
   1552 	my $funcdata_ref = $_[0]->{"func"};
   1553 	my $checkdata_ref = $_[0]->{"check"};
   1554 	my $testfncdata = $_[0]->{"testfnc"};
   1555 	my $sumfnccount = $_[0]->{"sumfnc"};
   1556 	my $testbrdata = $_[0]->{"testbr"};
   1557 	my $sumbrcount = $_[0]->{"sumbr"};
   1558 	my $lines_found = $_[0]->{"found"};
   1559 	my $lines_hit = $_[0]->{"hit"};
   1560 	my $fn_found = $_[0]->{"f_found"};
   1561 	my $fn_hit = $_[0]->{"f_hit"};
   1562 	my $br_found = $_[0]->{"b_found"};
   1563 	my $br_hit = $_[0]->{"b_hit"};
   1564 
   1565 	return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
   1566 		$testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
   1567 		$lines_found, $lines_hit, $fn_found, $fn_hit,
   1568 		$br_found, $br_hit);
   1569 }
   1570 
   1571 
   1572 #
   1573 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
   1574 #                checkdata_ref, testfncdata_ref, sumfcncount_ref,
   1575 #                testbrdata_ref, sumbrcount_ref[,lines_found,
   1576 #                lines_hit, f_found, f_hit, $b_found, $b_hit])
   1577 #
   1578 # Update the hash referenced by HASH_REF with the provided data references.
   1579 #
   1580 
   1581 sub set_info_entry($$$$$$$$$;$$$$$$)
   1582 {
   1583 	my $data_ref = $_[0];
   1584 
   1585 	$data_ref->{"test"} = $_[1];
   1586 	$data_ref->{"sum"} = $_[2];
   1587 	$data_ref->{"func"} = $_[3];
   1588 	$data_ref->{"check"} = $_[4];
   1589 	$data_ref->{"testfnc"} = $_[5];
   1590 	$data_ref->{"sumfnc"} = $_[6];
   1591 	$data_ref->{"testbr"} = $_[7];
   1592 	$data_ref->{"sumbr"} = $_[8];
   1593 
   1594 	if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
   1595 	if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
   1596 	if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
   1597 	if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
   1598 	if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
   1599 	if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
   1600 }
   1601 
   1602 
   1603 #
   1604 # add_counts(data1_ref, data2_ref)
   1605 #
   1606 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
   1607 #
   1608 #   line number -> execution count
   1609 #
   1610 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
   1611 # is a reference to a hash containing the combined mapping in which
   1612 # execution counts are added.
   1613 #
   1614 
   1615 sub add_counts($$)
   1616 {
   1617 	my %data1 = %{$_[0]};	# Hash 1
   1618 	my %data2 = %{$_[1]};	# Hash 2
   1619 	my %result;		# Resulting hash
   1620 	my $line;		# Current line iteration scalar
   1621 	my $data1_count;	# Count of line in hash1
   1622 	my $data2_count;	# Count of line in hash2
   1623 	my $found = 0;		# Total number of lines found
   1624 	my $hit = 0;		# Number of lines with a count > 0
   1625 
   1626 	foreach $line (keys(%data1))
   1627 	{
   1628 		$data1_count = $data1{$line};
   1629 		$data2_count = $data2{$line};
   1630 
   1631 		# Add counts if present in both hashes
   1632 		if (defined($data2_count)) { $data1_count += $data2_count; }
   1633 
   1634 		# Store sum in %result
   1635 		$result{$line} = $data1_count;
   1636 
   1637 		$found++;
   1638 		if ($data1_count > 0) { $hit++; }
   1639 	}
   1640 
   1641 	# Add lines unique to data2
   1642 	foreach $line (keys(%data2))
   1643 	{
   1644 		# Skip lines already in data1
   1645 		if (defined($data1{$line})) { next; }
   1646 
   1647 		# Copy count from data2
   1648 		$result{$line} = $data2{$line};
   1649 
   1650 		$found++;
   1651 		if ($result{$line} > 0) { $hit++; }
   1652 	}
   1653 
   1654 	return (\%result, $found, $hit);
   1655 }
   1656 
   1657 
   1658 #
   1659 # merge_checksums(ref1, ref2, filename)
   1660 #
   1661 # REF1 and REF2 are references to hashes containing a mapping
   1662 #
   1663 #   line number -> checksum
   1664 #
   1665 # Merge checksum lists defined in REF1 and REF2 and return reference to
   1666 # resulting hash. Die if a checksum for a line is defined in both hashes
   1667 # but does not match.
   1668 #
   1669 
   1670 sub merge_checksums($$$)
   1671 {
   1672 	my $ref1 = $_[0];
   1673 	my $ref2 = $_[1];
   1674 	my $filename = $_[2];
   1675 	my %result;
   1676 	my $line;
   1677 
   1678 	foreach $line (keys(%{$ref1}))
   1679 	{
   1680 		if (defined($ref2->{$line}) &&
   1681 		    ($ref1->{$line} ne $ref2->{$line}))
   1682 		{
   1683 			die("ERROR: checksum mismatch at $filename:$line\n");
   1684 		}
   1685 		$result{$line} = $ref1->{$line};
   1686 	}
   1687 
   1688 	foreach $line (keys(%{$ref2}))
   1689 	{
   1690 		$result{$line} = $ref2->{$line};
   1691 	}
   1692 
   1693 	return \%result;
   1694 }
   1695 
   1696 
   1697 #
   1698 # merge_func_data(funcdata1, funcdata2, filename)
   1699 #
   1700 
   1701 sub merge_func_data($$$)
   1702 {
   1703 	my ($funcdata1, $funcdata2, $filename) = @_;
   1704 	my %result;
   1705 	my $func;
   1706 
   1707 	if (defined($funcdata1)) {
   1708 		%result = %{$funcdata1};
   1709 	}
   1710 
   1711 	foreach $func (keys(%{$funcdata2})) {
   1712 		my $line1 = $result{$func};
   1713 		my $line2 = $funcdata2->{$func};
   1714 
   1715 		if (defined($line1) && ($line1 != $line2)) {
   1716 			warn("WARNING: function data mismatch at ".
   1717 			     "$filename:$line2\n");
   1718 			next;
   1719 		}
   1720 		$result{$func} = $line2;
   1721 	}
   1722 
   1723 	return \%result;
   1724 }
   1725 
   1726 
   1727 #
   1728 # add_fnccount(fnccount1, fnccount2)
   1729 #
   1730 # Add function call count data. Return list (fnccount_added, f_found, f_hit)
   1731 #
   1732 
   1733 sub add_fnccount($$)
   1734 {
   1735 	my ($fnccount1, $fnccount2) = @_;
   1736 	my %result;
   1737 	my $fn_found;
   1738 	my $fn_hit;
   1739 	my $function;
   1740 
   1741 	if (defined($fnccount1)) {
   1742 		%result = %{$fnccount1};
   1743 	}
   1744 	foreach $function (keys(%{$fnccount2})) {
   1745 		$result{$function} += $fnccount2->{$function};
   1746 	}
   1747 	$fn_found = scalar(keys(%result));
   1748 	$fn_hit = 0;
   1749 	foreach $function (keys(%result)) {
   1750 		if ($result{$function} > 0) {
   1751 			$fn_hit++;
   1752 		}
   1753 	}
   1754 
   1755 	return (\%result, $fn_found, $fn_hit);
   1756 }
   1757 
   1758 #
   1759 # add_testfncdata(testfncdata1, testfncdata2)
   1760 #
   1761 # Add function call count data for several tests. Return reference to
   1762 # added_testfncdata.
   1763 #
   1764 
   1765 sub add_testfncdata($$)
   1766 {
   1767 	my ($testfncdata1, $testfncdata2) = @_;
   1768 	my %result;
   1769 	my $testname;
   1770 
   1771 	foreach $testname (keys(%{$testfncdata1})) {
   1772 		if (defined($testfncdata2->{$testname})) {
   1773 			my $fnccount;
   1774 
   1775 			# Function call count data for this testname exists
   1776 			# in both data sets: add
   1777 			($fnccount) = add_fnccount(
   1778 				$testfncdata1->{$testname},
   1779 				$testfncdata2->{$testname});
   1780 			$result{$testname} = $fnccount;
   1781 			next;
   1782 		}
   1783 		# Function call count data for this testname is unique to
   1784 		# data set 1: copy
   1785 		$result{$testname} = $testfncdata1->{$testname};
   1786 	}
   1787 
   1788 	# Add count data for testnames unique to data set 2
   1789 	foreach $testname (keys(%{$testfncdata2})) {
   1790 		if (!defined($result{$testname})) {
   1791 			$result{$testname} = $testfncdata2->{$testname};
   1792 		}
   1793 	}
   1794 	return \%result;
   1795 }
   1796 
   1797 
   1798 #
   1799 # brcount_to_db(brcount)
   1800 #
   1801 # Convert brcount data to the following format:
   1802 #
   1803 # db:          line number    -> block hash
   1804 # block hash:  block number   -> branch hash
   1805 # branch hash: branch number  -> taken value
   1806 #
   1807 
   1808 sub brcount_to_db($)
   1809 {
   1810 	my ($brcount) = @_;
   1811 	my $line;
   1812 	my $db;
   1813 
   1814 	# Add branches from first count to database
   1815 	foreach $line (keys(%{$brcount})) {
   1816 		my $brdata = $brcount->{$line};
   1817 		my $i;
   1818 		my $num = br_ivec_len($brdata);
   1819 
   1820 		for ($i = 0; $i < $num; $i++) {
   1821 			my ($block, $branch, $taken) = br_ivec_get($brdata, $i);
   1822 
   1823 			$db->{$line}->{$block}->{$branch} = $taken;
   1824 		}
   1825 	}
   1826 
   1827 	return $db;
   1828 }
   1829 
   1830 
   1831 #
   1832 # db_to_brcount(db)
   1833 #
   1834 # Convert branch coverage data back to brcount format.
   1835 #
   1836 
   1837 sub db_to_brcount($)
   1838 {
   1839 	my ($db) = @_;
   1840 	my $line;
   1841 	my $brcount = {};
   1842 	my $br_found = 0;
   1843 	my $br_hit = 0;
   1844 
   1845 	# Convert database back to brcount format
   1846 	foreach $line (sort({$a <=> $b} keys(%{$db}))) {
   1847 		my $ldata = $db->{$line};
   1848 		my $brdata;
   1849 		my $block;
   1850 
   1851 		foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
   1852 			my $bdata = $ldata->{$block};
   1853 			my $branch;
   1854 
   1855 			foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
   1856 				my $taken = $bdata->{$branch};
   1857 
   1858 				$br_found++;
   1859 				$br_hit++ if ($taken ne "-" && $taken > 0);
   1860 				$brdata = br_ivec_push($brdata, $block,
   1861 						       $branch, $taken);
   1862 			}
   1863 		}
   1864 		$brcount->{$line} = $brdata;
   1865 	}
   1866 
   1867 	return ($brcount, $br_found, $br_hit);
   1868 }
   1869 
   1870 
   1871 #
   1872 # combine_brcount(brcount1, brcount2, type)
   1873 #
   1874 # If add is BR_ADD, add branch coverage data and return list (brcount_added,
   1875 # br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2
   1876 # from brcount1 and return (brcount_sub, br_found, br_hit).
   1877 #
   1878 
   1879 sub combine_brcount($$$)
   1880 {
   1881 	my ($brcount1, $brcount2, $type) = @_;
   1882 	my $line;
   1883 	my $block;
   1884 	my $branch;
   1885 	my $taken;
   1886 	my $db;
   1887 	my $br_found = 0;
   1888 	my $br_hit = 0;
   1889 	my $result;
   1890 
   1891 	# Convert branches from first count to database
   1892 	$db = brcount_to_db($brcount1);
   1893 	# Combine values from database and second count
   1894 	foreach $line (keys(%{$brcount2})) {
   1895 		my $brdata = $brcount2->{$line};
   1896 		my $num = br_ivec_len($brdata);
   1897 		my $i;
   1898 
   1899 		for ($i = 0; $i < $num; $i++) {
   1900 			($block, $branch, $taken) = br_ivec_get($brdata, $i);
   1901 			my $new_taken = $db->{$line}->{$block}->{$branch};
   1902 
   1903 			if ($type == $BR_ADD) {
   1904 				$new_taken = br_taken_add($new_taken, $taken);
   1905 			} elsif ($type == $BR_SUB) {
   1906 				$new_taken = br_taken_sub($new_taken, $taken);
   1907 			}
   1908 			$db->{$line}->{$block}->{$branch} = $new_taken
   1909 				if (defined($new_taken));
   1910 		}
   1911 	}
   1912 	# Convert database back to brcount format
   1913 	($result, $br_found, $br_hit) = db_to_brcount($db);
   1914 
   1915 	return ($result, $br_found, $br_hit);
   1916 }
   1917 
   1918 
   1919 #
   1920 # add_testbrdata(testbrdata1, testbrdata2)
   1921 #
   1922 # Add branch coverage data for several tests. Return reference to
   1923 # added_testbrdata.
   1924 #
   1925 
   1926 sub add_testbrdata($$)
   1927 {
   1928 	my ($testbrdata1, $testbrdata2) = @_;
   1929 	my %result;
   1930 	my $testname;
   1931 
   1932 	foreach $testname (keys(%{$testbrdata1})) {
   1933 		if (defined($testbrdata2->{$testname})) {
   1934 			my $brcount;
   1935 
   1936 			# Branch coverage data for this testname exists
   1937 			# in both data sets: add
   1938 			($brcount) = combine_brcount($testbrdata1->{$testname},
   1939 					 $testbrdata2->{$testname}, $BR_ADD);
   1940 			$result{$testname} = $brcount;
   1941 			next;
   1942 		}
   1943 		# Branch coverage data for this testname is unique to
   1944 		# data set 1: copy
   1945 		$result{$testname} = $testbrdata1->{$testname};
   1946 	}
   1947 
   1948 	# Add count data for testnames unique to data set 2
   1949 	foreach $testname (keys(%{$testbrdata2})) {
   1950 		if (!defined($result{$testname})) {
   1951 			$result{$testname} = $testbrdata2->{$testname};
   1952 		}
   1953 	}
   1954 	return \%result;
   1955 }
   1956 
   1957 
   1958 #
   1959 # combine_info_entries(entry_ref1, entry_ref2, filename)
   1960 #
   1961 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
   1962 # Return reference to resulting hash.
   1963 #
   1964 
   1965 sub combine_info_entries($$$)
   1966 {
   1967 	my $entry1 = $_[0];	# Reference to hash containing first entry
   1968 	my $testdata1;
   1969 	my $sumcount1;
   1970 	my $funcdata1;
   1971 	my $checkdata1;
   1972 	my $testfncdata1;
   1973 	my $sumfnccount1;
   1974 	my $testbrdata1;
   1975 	my $sumbrcount1;
   1976 
   1977 	my $entry2 = $_[1];	# Reference to hash containing second entry
   1978 	my $testdata2;
   1979 	my $sumcount2;
   1980 	my $funcdata2;
   1981 	my $checkdata2;
   1982 	my $testfncdata2;
   1983 	my $sumfnccount2;
   1984 	my $testbrdata2;
   1985 	my $sumbrcount2;
   1986 
   1987 	my %result;		# Hash containing combined entry
   1988 	my %result_testdata;
   1989 	my $result_sumcount = {};
   1990 	my $result_funcdata;
   1991 	my $result_testfncdata;
   1992 	my $result_sumfnccount;
   1993 	my $result_testbrdata;
   1994 	my $result_sumbrcount;
   1995 	my $lines_found;
   1996 	my $lines_hit;
   1997 	my $fn_found;
   1998 	my $fn_hit;
   1999 	my $br_found;
   2000 	my $br_hit;
   2001 
   2002 	my $testname;
   2003 	my $filename = $_[2];
   2004 
   2005 	# Retrieve data
   2006 	($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
   2007 	 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
   2008 	($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
   2009 	 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
   2010 
   2011 	# Merge checksums
   2012 	$checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
   2013 
   2014 	# Combine funcdata
   2015 	$result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
   2016 
   2017 	# Combine function call count data
   2018 	$result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
   2019 	($result_sumfnccount, $fn_found, $fn_hit) =
   2020 		add_fnccount($sumfnccount1, $sumfnccount2);
   2021 	
   2022 	# Combine branch coverage data
   2023 	$result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
   2024 	($result_sumbrcount, $br_found, $br_hit) =
   2025 		combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
   2026 
   2027 	# Combine testdata
   2028 	foreach $testname (keys(%{$testdata1}))
   2029 	{
   2030 		if (defined($testdata2->{$testname}))
   2031 		{
   2032 			# testname is present in both entries, requires
   2033 			# combination
   2034 			($result_testdata{$testname}) =
   2035 				add_counts($testdata1->{$testname},
   2036 					   $testdata2->{$testname});
   2037 		}
   2038 		else
   2039 		{
   2040 			# testname only present in entry1, add to result
   2041 			$result_testdata{$testname} = $testdata1->{$testname};
   2042 		}
   2043 
   2044 		# update sum count hash
   2045 		($result_sumcount, $lines_found, $lines_hit) =
   2046 			add_counts($result_sumcount,
   2047 				   $result_testdata{$testname});
   2048 	}
   2049 
   2050 	foreach $testname (keys(%{$testdata2}))
   2051 	{
   2052 		# Skip testnames already covered by previous iteration
   2053 		if (defined($testdata1->{$testname})) { next; }
   2054 
   2055 		# testname only present in entry2, add to result hash
   2056 		$result_testdata{$testname} = $testdata2->{$testname};
   2057 
   2058 		# update sum count hash
   2059 		($result_sumcount, $lines_found, $lines_hit) =
   2060 			add_counts($result_sumcount,
   2061 				   $result_testdata{$testname});
   2062 	}
   2063 	
   2064 	# Calculate resulting sumcount
   2065 
   2066 	# Store result
   2067 	set_info_entry(\%result, \%result_testdata, $result_sumcount,
   2068 		       $result_funcdata, $checkdata1, $result_testfncdata,
   2069 		       $result_sumfnccount, $result_testbrdata,
   2070 		       $result_sumbrcount, $lines_found, $lines_hit,
   2071 		       $fn_found, $fn_hit, $br_found, $br_hit);
   2072 
   2073 	return(\%result);
   2074 }
   2075 
   2076 
   2077 #
   2078 # combine_info_files(info_ref1, info_ref2)
   2079 #
   2080 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
   2081 # reference to resulting hash.
   2082 #
   2083 
   2084 sub combine_info_files($$)
   2085 {
   2086 	my %hash1 = %{$_[0]};
   2087 	my %hash2 = %{$_[1]};
   2088 	my $filename;
   2089 
   2090 	foreach $filename (keys(%hash2))
   2091 	{
   2092 		if ($hash1{$filename})
   2093 		{
   2094 			# Entry already exists in hash1, combine them
   2095 			$hash1{$filename} =
   2096 				combine_info_entries($hash1{$filename},
   2097 						     $hash2{$filename},
   2098 						     $filename);
   2099 		}
   2100 		else
   2101 		{
   2102 			# Entry is unique in both hashes, simply add to
   2103 			# resulting hash
   2104 			$hash1{$filename} = $hash2{$filename};
   2105 		}
   2106 	}
   2107 
   2108 	return(\%hash1);
   2109 }
   2110 
   2111 
   2112 #
   2113 # get_prefix(filename_list)
   2114 #
   2115 # Search FILENAME_LIST for a directory prefix which is common to as many
   2116 # list entries as possible, so that removing this prefix will minimize the
   2117 # sum of the lengths of all resulting shortened filenames.
   2118 #
   2119 
   2120 sub get_prefix(@)
   2121 {
   2122 	my @filename_list = @_;		# provided list of filenames
   2123 	my %prefix;			# mapping: prefix -> sum of lengths
   2124 	my $current;			# Temporary iteration variable
   2125 
   2126 	# Find list of prefixes
   2127 	foreach (@filename_list)
   2128 	{
   2129 		# Need explicit assignment to get a copy of $_ so that
   2130 		# shortening the contained prefix does not affect the list
   2131 		$current = shorten_prefix($_);
   2132 		while ($current = shorten_prefix($current))
   2133 		{
   2134 			# Skip rest if the remaining prefix has already been
   2135 			# added to hash
   2136 			if ($prefix{$current}) { last; }
   2137 
   2138 			# Initialize with 0
   2139 			$prefix{$current}="0";
   2140 		}
   2141 
   2142 	}
   2143 
   2144 	# Calculate sum of lengths for all prefixes
   2145 	foreach $current (keys(%prefix))
   2146 	{
   2147 		foreach (@filename_list)
   2148 		{
   2149 			# Add original length
   2150 			$prefix{$current} += length($_);
   2151 
   2152 			# Check whether prefix matches
   2153 			if (substr($_, 0, length($current)) eq $current)
   2154 			{
   2155 				# Subtract prefix length for this filename
   2156 				$prefix{$current} -= length($current);
   2157 			}
   2158 		}
   2159 	}
   2160 
   2161 	# Find and return prefix with minimal sum
   2162 	$current = (keys(%prefix))[0];
   2163 
   2164 	foreach (keys(%prefix))
   2165 	{
   2166 		if ($prefix{$_} < $prefix{$current})
   2167 		{
   2168 			$current = $_;
   2169 		}
   2170 	}
   2171 
   2172 	return($current);
   2173 }
   2174 
   2175 
   2176 #
   2177 # shorten_prefix(prefix)
   2178 #
   2179 # Return PREFIX shortened by last directory component.
   2180 #
   2181 
   2182 sub shorten_prefix($)
   2183 {
   2184 	my @list = split("/", $_[0]);
   2185 
   2186 	pop(@list);
   2187 	return join("/", @list);
   2188 }
   2189 
   2190 
   2191 
   2192 #
   2193 # get_dir_list(filename_list)
   2194 #
   2195 # Return sorted list of directories for each entry in given FILENAME_LIST.
   2196 #
   2197 
   2198 sub get_dir_list(@)
   2199 {
   2200 	my %result;
   2201 
   2202 	foreach (@_)
   2203 	{
   2204 		$result{shorten_prefix($_)} = "";
   2205 	}
   2206 
   2207 	return(sort(keys(%result)));
   2208 }
   2209 
   2210 
   2211 #
   2212 # get_relative_base_path(subdirectory)
   2213 #
   2214 # Return a relative path string which references the base path when applied
   2215 # in SUBDIRECTORY.
   2216 #
   2217 # Example: get_relative_base_path("fs/mm") -> "../../"
   2218 #
   2219 
   2220 sub get_relative_base_path($)
   2221 {
   2222 	my $result = "";
   2223 	my $index;
   2224 
   2225 	# Make an empty directory path a special case
   2226 	if (!$_[0]) { return(""); }
   2227 
   2228 	# Count number of /s in path
   2229 	$index = ($_[0] =~ s/\//\//g);
   2230 
   2231 	# Add a ../ to $result for each / in the directory path + 1
   2232 	for (; $index>=0; $index--)
   2233 	{
   2234 		$result .= "../";
   2235 	}
   2236 
   2237 	return $result;
   2238 }
   2239 
   2240 
   2241 #
   2242 # read_testfile(test_filename)
   2243 #
   2244 # Read in file TEST_FILENAME which contains test descriptions in the format:
   2245 #
   2246 #   TN:<whitespace><test name>
   2247 #   TD:<whitespace><test description>
   2248 #
   2249 # for each test case. Return a reference to a hash containing a mapping
   2250 #
   2251 #   test name -> test description.
   2252 #
   2253 # Die on error.
   2254 #
   2255 
   2256 sub read_testfile($)
   2257 {
   2258 	my %result;
   2259 	my $test_name;
   2260 	my $changed_testname;
   2261 	local *TEST_HANDLE;
   2262 
   2263 	open(TEST_HANDLE, "<".$_[0])
   2264 		or die("ERROR: cannot open $_[0]!\n");
   2265 
   2266 	while (<TEST_HANDLE>)
   2267 	{
   2268 		chomp($_);
   2269 
   2270 		# Match lines beginning with TN:<whitespace(s)>
   2271 		if (/^TN:\s+(.*?)\s*$/)
   2272 		{
   2273 			# Store name for later use
   2274 			$test_name = $1;
   2275 			if ($test_name =~ s/\W/_/g)
   2276 			{
   2277 				$changed_testname = 1;
   2278 			}
   2279 		}
   2280 
   2281 		# Match lines beginning with TD:<whitespace(s)>
   2282 		if (/^TD:\s+(.*?)\s*$/)
   2283 		{
   2284 			# Check for empty line
   2285 			if ($1)
   2286 			{
   2287 				# Add description to hash
   2288 				$result{$test_name} .= " $1";
   2289 			}
   2290 			else
   2291 			{
   2292 				# Add empty line
   2293 				$result{$test_name} .= "\n\n";
   2294 			}
   2295 		}
   2296 	}
   2297 
   2298 	close(TEST_HANDLE);
   2299 
   2300 	if ($changed_testname)
   2301 	{
   2302 		warn("WARNING: invalid characters removed from testname in ".
   2303 		     "descriptions file $_[0]\n");
   2304 	}
   2305 
   2306 	return \%result;
   2307 }
   2308 
   2309 
   2310 #
   2311 # escape_html(STRING)
   2312 #
   2313 # Return a copy of STRING in which all occurrences of HTML special characters
   2314 # are escaped.
   2315 #
   2316 
   2317 sub escape_html($)
   2318 {
   2319 	my $string = $_[0];
   2320 
   2321 	if (!$string) { return ""; }
   2322 
   2323 	$string =~ s/&/&amp;/g;		# & -> &amp;
   2324 	$string =~ s/</&lt;/g;		# < -> &lt;
   2325 	$string =~ s/>/&gt;/g;		# > -> &gt;
   2326 	$string =~ s/\"/&quot;/g;	# " -> &quot;
   2327 
   2328 	while ($string =~ /^([^\t]*)(\t)/)
   2329 	{
   2330 		my $replacement = " "x($tab_size - (length($1) % $tab_size));
   2331 		$string =~ s/^([^\t]*)(\t)/$1$replacement/;
   2332 	}
   2333 
   2334 	$string =~ s/\n/<br>/g;		# \n -> <br>
   2335 
   2336 	return $string;
   2337 }
   2338 
   2339 
   2340 #
   2341 # get_date_string()
   2342 #
   2343 # Return the current date in the form: yyyy-mm-dd
   2344 #
   2345 
   2346 sub get_date_string()
   2347 {
   2348 	my $year;
   2349 	my $month;
   2350 	my $day;
   2351 
   2352 	($year, $month, $day) = (localtime())[5, 4, 3];
   2353 
   2354 	return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day);
   2355 }
   2356 
   2357 
   2358 #
   2359 # create_sub_dir(dir_name)
   2360 #
   2361 # Create subdirectory DIR_NAME if it does not already exist, including all its
   2362 # parent directories.
   2363 #
   2364 # Die on error.
   2365 #
   2366 
   2367 sub create_sub_dir($)
   2368 {
   2369 	my ($dir) = @_;
   2370 
   2371 	system("mkdir", "-p" ,$dir)
   2372 		and die("ERROR: cannot create directory $dir!\n");
   2373 }
   2374 
   2375 
   2376 #
   2377 # write_description_file(descriptions, overall_found, overall_hit,
   2378 #                        total_fn_found, total_fn_hit, total_br_found,
   2379 #                        total_br_hit)
   2380 #
   2381 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a
   2382 # reference to a hash containing a mapping
   2383 #
   2384 #   test case name -> test case description
   2385 #
   2386 # Die on error.
   2387 #
   2388 
   2389 sub write_description_file($$$$$$$)
   2390 {
   2391 	my %description = %{$_[0]};
   2392 	my $found = $_[1];
   2393 	my $hit = $_[2];
   2394 	my $fn_found = $_[3];
   2395 	my $fn_hit = $_[4];
   2396 	my $br_found = $_[5];
   2397 	my $br_hit = $_[6];
   2398 	my $test_name;
   2399 	local *HTML_HANDLE;
   2400 
   2401 	html_create(*HTML_HANDLE,"descriptions.$html_ext");
   2402 	write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions");
   2403 	write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found,
   2404 		     $fn_hit, $br_found, $br_hit, 0);
   2405 
   2406 	write_test_table_prolog(*HTML_HANDLE,
   2407 			 "Test case descriptions - alphabetical list");
   2408 
   2409 	foreach $test_name (sort(keys(%description)))
   2410 	{
   2411 		write_test_table_entry(*HTML_HANDLE, $test_name,
   2412 				       escape_html($description{$test_name}));
   2413 	}
   2414 
   2415 	write_test_table_epilog(*HTML_HANDLE);
   2416 	write_html_epilog(*HTML_HANDLE, "");
   2417 
   2418 	close(*HTML_HANDLE);
   2419 }
   2420 
   2421 
   2422 
   2423 #
   2424 # write_png_files()
   2425 #
   2426 # Create all necessary .png files for the HTML-output in the current
   2427 # directory. .png-files are used as bar graphs.
   2428 #
   2429 # Die on error.
   2430 #
   2431 
   2432 sub write_png_files()
   2433 {
   2434 	my %data;
   2435 	local *PNG_HANDLE;
   2436 
   2437 	$data{"ruby.png"} =
   2438 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2439 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2440 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2441 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2442 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57, 
   2443 		 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2444 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2445 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2446 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2447 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f, 
   2448 		 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00, 
   2449 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2450 		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2451 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2452 		 0x82];
   2453 	$data{"amber.png"} =
   2454 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2455 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2456 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2457 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2458 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb, 
   2459 		 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2460 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2461 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2462 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2463 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50, 
   2464 		 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00, 
   2465 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2466 	  	 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2467 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2468 		 0x82];
   2469 	$data{"emerald.png"} =
   2470 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2471 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2472 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2473 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2474 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5, 
   2475 		 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2476 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2477 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2478 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2479 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59, 
   2480 		 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00, 
   2481 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2482 		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2483 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2484 		 0x82];
   2485 	$data{"snow.png"} =
   2486 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2487 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2488 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2489 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2490 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc, 
   2491 		 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2492 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2493 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2494 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2495 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 
   2496 		 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 
   2497 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2498 		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2499 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2500 		 0x82];
   2501 	$data{"glass.png"} =
   2502 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2503 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2504 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2505 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2506 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2507 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 
   2508 		 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 
   2509 		 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, 
   2510 		 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88, 
   2511 		 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 
   2512 		 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 
   2513 		 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 
   2514 		 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4, 
   2515 		 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 
   2516 		 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 
   2517 		 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49, 
   2518 		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82];
   2519 	$data{"updown.png"} =
   2520 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2521 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a, 
   2522 		 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, 
   2523 		 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41, 
   2524 		 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00, 
   2525 		 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23, 
   2526 		 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08, 
   2527 		 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60, 
   2528 		 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62, 
   2529 		 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f, 
   2530 		 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49, 
   2531 		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);
   2532 	foreach (keys(%data))
   2533 	{
   2534 		open(PNG_HANDLE, ">".$_)
   2535 			or die("ERROR: cannot create $_!\n");
   2536 		binmode(PNG_HANDLE);
   2537 		print(PNG_HANDLE map(chr,@{$data{$_}}));
   2538 		close(PNG_HANDLE);
   2539 	}
   2540 }
   2541 
   2542 
   2543 #
   2544 # write_htaccess_file()
   2545 #
   2546 
   2547 sub write_htaccess_file()
   2548 {
   2549 	local *HTACCESS_HANDLE;
   2550 	my $htaccess_data;
   2551 
   2552 	open(*HTACCESS_HANDLE, ">.htaccess")
   2553 		or die("ERROR: cannot open .htaccess for writing!\n");
   2554 
   2555 	$htaccess_data = (<<"END_OF_HTACCESS")
   2556 AddEncoding x-gzip .html
   2557 END_OF_HTACCESS
   2558 	;
   2559 
   2560 	print(HTACCESS_HANDLE $htaccess_data);
   2561 	close(*HTACCESS_HANDLE);
   2562 }
   2563 
   2564 
   2565 #
   2566 # write_css_file()
   2567 #
   2568 # Write the cascading style sheet file gcov.css to the current directory.
   2569 # This file defines basic layout attributes of all generated HTML pages.
   2570 #
   2571 
   2572 sub write_css_file()
   2573 {
   2574 	local *CSS_HANDLE;
   2575 
   2576 	# Check for a specified external style sheet file
   2577 	if ($css_filename)
   2578 	{
   2579 		# Simply copy that file
   2580 		system("cp", $css_filename, "gcov.css")
   2581 			and die("ERROR: cannot copy file $css_filename!\n");
   2582 		return;
   2583 	}
   2584 
   2585 	open(CSS_HANDLE, ">gcov.css")
   2586 		or die ("ERROR: cannot open gcov.css for writing!\n");
   2587 
   2588 
   2589 	# *************************************************************
   2590 
   2591 	my $css_data = ($_=<<"END_OF_CSS")
   2592 	/* All views: initial background and text color */
   2593 	body
   2594 	{
   2595 	  color: #000000;
   2596 	  background-color: #FFFFFF;
   2597 	}
   2598 	
   2599 	/* All views: standard link format*/
   2600 	a:link
   2601 	{
   2602 	  color: #284FA8;
   2603 	  text-decoration: underline;
   2604 	}
   2605 	
   2606 	/* All views: standard link - visited format */
   2607 	a:visited
   2608 	{
   2609 	  color: #00CB40;
   2610 	  text-decoration: underline;
   2611 	}
   2612 	
   2613 	/* All views: standard link - activated format */
   2614 	a:active
   2615 	{
   2616 	  color: #FF0040;
   2617 	  text-decoration: underline;
   2618 	}
   2619 	
   2620 	/* All views: main title format */
   2621 	td.title
   2622 	{
   2623 	  text-align: center;
   2624 	  padding-bottom: 10px;
   2625 	  font-family: sans-serif;
   2626 	  font-size: 20pt;
   2627 	  font-style: italic;
   2628 	  font-weight: bold;
   2629 	}
   2630 	
   2631 	/* All views: header item format */
   2632 	td.headerItem
   2633 	{
   2634 	  text-align: right;
   2635 	  padding-right: 6px;
   2636 	  font-family: sans-serif;
   2637 	  font-weight: bold;
   2638 	  vertical-align: top;
   2639 	  white-space: nowrap;
   2640 	}
   2641 	
   2642 	/* All views: header item value format */
   2643 	td.headerValue
   2644 	{
   2645 	  text-align: left;
   2646 	  color: #284FA8;
   2647 	  font-family: sans-serif;
   2648 	  font-weight: bold;
   2649 	  white-space: nowrap;
   2650 	}
   2651 
   2652 	/* All views: header item coverage table heading */
   2653 	td.headerCovTableHead
   2654 	{
   2655 	  text-align: center;
   2656 	  padding-right: 6px;
   2657 	  padding-left: 6px;
   2658 	  padding-bottom: 0px;
   2659 	  font-family: sans-serif;
   2660 	  font-size: 80%;
   2661 	  white-space: nowrap;
   2662 	}
   2663 	
   2664 	/* All views: header item coverage table entry */
   2665 	td.headerCovTableEntry
   2666 	{
   2667 	  text-align: right;
   2668 	  color: #284FA8;
   2669 	  font-family: sans-serif;
   2670 	  font-weight: bold;
   2671 	  white-space: nowrap;
   2672 	  padding-left: 12px;
   2673 	  padding-right: 4px;
   2674 	  background-color: #DAE7FE;
   2675 	}
   2676 	
   2677 	/* All views: header item coverage table entry for high coverage rate */
   2678 	td.headerCovTableEntryHi
   2679 	{
   2680 	  text-align: right;
   2681 	  color: #000000;
   2682 	  font-family: sans-serif;
   2683 	  font-weight: bold;
   2684 	  white-space: nowrap;
   2685 	  padding-left: 12px;
   2686 	  padding-right: 4px;
   2687 	  background-color: #A7FC9D;
   2688 	}
   2689 	
   2690 	/* All views: header item coverage table entry for medium coverage rate */
   2691 	td.headerCovTableEntryMed
   2692 	{
   2693 	  text-align: right;
   2694 	  color: #000000;
   2695 	  font-family: sans-serif;
   2696 	  font-weight: bold;
   2697 	  white-space: nowrap;
   2698 	  padding-left: 12px;
   2699 	  padding-right: 4px;
   2700 	  background-color: #FFEA20;
   2701 	}
   2702 	
   2703 	/* All views: header item coverage table entry for ow coverage rate */
   2704 	td.headerCovTableEntryLo
   2705 	{
   2706 	  text-align: right;
   2707 	  color: #000000;
   2708 	  font-family: sans-serif;
   2709 	  font-weight: bold;
   2710 	  white-space: nowrap;
   2711 	  padding-left: 12px;
   2712 	  padding-right: 4px;
   2713 	  background-color: #FF0000;
   2714 	}
   2715 	
   2716 	/* All views: header legend value for legend entry */
   2717 	td.headerValueLeg
   2718 	{
   2719 	  text-align: left;
   2720 	  color: #000000;
   2721 	  font-family: sans-serif;
   2722 	  font-size: 80%;
   2723 	  white-space: nowrap;
   2724 	  padding-top: 4px;
   2725 	}
   2726 	
   2727 	/* All views: color of horizontal ruler */
   2728 	td.ruler
   2729 	{
   2730 	  background-color: #6688D4;
   2731 	}
   2732 	
   2733 	/* All views: version string format */
   2734 	td.versionInfo
   2735 	{
   2736 	  text-align: center;
   2737 	  padding-top: 2px;
   2738 	  font-family: sans-serif;
   2739 	  font-style: italic;
   2740 	}
   2741 	
   2742 	/* Directory view/File view (all)/Test case descriptions:
   2743 	   table headline format */
   2744 	td.tableHead
   2745 	{
   2746 	  text-align: center;
   2747 	  color: #FFFFFF;
   2748 	  background-color: #6688D4;
   2749 	  font-family: sans-serif;
   2750 	  font-size: 120%;
   2751 	  font-weight: bold;
   2752 	  white-space: nowrap;
   2753 	  padding-left: 4px;
   2754 	  padding-right: 4px;
   2755 	}
   2756 
   2757 	span.tableHeadSort
   2758 	{
   2759 	  padding-right: 4px;
   2760 	}
   2761 	
   2762 	/* Directory view/File view (all): filename entry format */
   2763 	td.coverFile
   2764 	{
   2765 	  text-align: left;
   2766 	  padding-left: 10px;
   2767 	  padding-right: 20px; 
   2768 	  color: #284FA8;
   2769 	  background-color: #DAE7FE;
   2770 	  font-family: monospace;
   2771 	}
   2772 	
   2773 	/* Directory view/File view (all): bar-graph entry format*/
   2774 	td.coverBar
   2775 	{
   2776 	  padding-left: 10px;
   2777 	  padding-right: 10px;
   2778 	  background-color: #DAE7FE;
   2779 	}
   2780 	
   2781 	/* Directory view/File view (all): bar-graph outline color */
   2782 	td.coverBarOutline
   2783 	{
   2784 	  background-color: #000000;
   2785 	}
   2786 	
   2787 	/* Directory view/File view (all): percentage entry for files with
   2788 	   high coverage rate */
   2789 	td.coverPerHi
   2790 	{
   2791 	  text-align: right;
   2792 	  padding-left: 10px;
   2793 	  padding-right: 10px;
   2794 	  background-color: #A7FC9D;
   2795 	  font-weight: bold;
   2796 	  font-family: sans-serif;
   2797 	}
   2798 	
   2799 	/* Directory view/File view (all): line count entry for files with
   2800 	   high coverage rate */
   2801 	td.coverNumHi
   2802 	{
   2803 	  text-align: right;
   2804 	  padding-left: 10px;
   2805 	  padding-right: 10px;
   2806 	  background-color: #A7FC9D;
   2807 	  white-space: nowrap;
   2808 	  font-family: sans-serif;
   2809 	}
   2810 	
   2811 	/* Directory view/File view (all): percentage entry for files with
   2812 	   medium coverage rate */
   2813 	td.coverPerMed
   2814 	{
   2815 	  text-align: right;
   2816 	  padding-left: 10px;
   2817 	  padding-right: 10px;
   2818 	  background-color: #FFEA20;
   2819 	  font-weight: bold;
   2820 	  font-family: sans-serif;
   2821 	}
   2822 	
   2823 	/* Directory view/File view (all): line count entry for files with
   2824 	   medium coverage rate */
   2825 	td.coverNumMed
   2826 	{
   2827 	  text-align: right;
   2828 	  padding-left: 10px;
   2829 	  padding-right: 10px;
   2830 	  background-color: #FFEA20;
   2831 	  white-space: nowrap;
   2832 	  font-family: sans-serif;
   2833 	}
   2834 	
   2835 	/* Directory view/File view (all): percentage entry for files with
   2836 	   low coverage rate */
   2837 	td.coverPerLo
   2838 	{
   2839 	  text-align: right;
   2840 	  padding-left: 10px;
   2841 	  padding-right: 10px;
   2842 	  background-color: #FF0000;
   2843 	  font-weight: bold;
   2844 	  font-family: sans-serif;
   2845 	}
   2846 	
   2847 	/* Directory view/File view (all): line count entry for files with
   2848 	   low coverage rate */
   2849 	td.coverNumLo
   2850 	{
   2851 	  text-align: right;
   2852 	  padding-left: 10px;
   2853 	  padding-right: 10px;
   2854 	  background-color: #FF0000;
   2855 	  white-space: nowrap;
   2856 	  font-family: sans-serif;
   2857 	}
   2858 	
   2859 	/* File view (all): "show/hide details" link format */
   2860 	a.detail:link
   2861 	{
   2862 	  color: #B8D0FF;
   2863 	  font-size:80%;
   2864 	}
   2865 	
   2866 	/* File view (all): "show/hide details" link - visited format */
   2867 	a.detail:visited
   2868 	{
   2869 	  color: #B8D0FF;
   2870 	  font-size:80%;
   2871 	}
   2872 	
   2873 	/* File view (all): "show/hide details" link - activated format */
   2874 	a.detail:active
   2875 	{
   2876 	  color: #FFFFFF;
   2877 	  font-size:80%;
   2878 	}
   2879 	
   2880 	/* File view (detail): test name entry */
   2881 	td.testName
   2882 	{
   2883 	  text-align: right;
   2884 	  padding-right: 10px;
   2885 	  background-color: #DAE7FE;
   2886 	  font-family: sans-serif;
   2887 	}
   2888 	
   2889 	/* File view (detail): test percentage entry */
   2890 	td.testPer
   2891 	{
   2892 	  text-align: right;
   2893 	  padding-left: 10px;
   2894 	  padding-right: 10px; 
   2895 	  background-color: #DAE7FE;
   2896 	  font-family: sans-serif;
   2897 	}
   2898 	
   2899 	/* File view (detail): test lines count entry */
   2900 	td.testNum
   2901 	{
   2902 	  text-align: right;
   2903 	  padding-left: 10px;
   2904 	  padding-right: 10px; 
   2905 	  background-color: #DAE7FE;
   2906 	  font-family: sans-serif;
   2907 	}
   2908 	
   2909 	/* Test case descriptions: test name format*/
   2910 	dt
   2911 	{
   2912 	  font-family: sans-serif;
   2913 	  font-weight: bold;
   2914 	}
   2915 	
   2916 	/* Test case descriptions: description table body */
   2917 	td.testDescription
   2918 	{
   2919 	  padding-top: 10px;
   2920 	  padding-left: 30px;
   2921 	  padding-bottom: 10px;
   2922 	  padding-right: 30px;
   2923 	  background-color: #DAE7FE;
   2924 	}
   2925 	
   2926 	/* Source code view: function entry */
   2927 	td.coverFn
   2928 	{
   2929 	  text-align: left;
   2930 	  padding-left: 10px;
   2931 	  padding-right: 20px; 
   2932 	  color: #284FA8;
   2933 	  background-color: #DAE7FE;
   2934 	  font-family: monospace;
   2935 	}
   2936 
   2937 	/* Source code view: function entry zero count*/
   2938 	td.coverFnLo
   2939 	{
   2940 	  text-align: right;
   2941 	  padding-left: 10px;
   2942 	  padding-right: 10px;
   2943 	  background-color: #FF0000;
   2944 	  font-weight: bold;
   2945 	  font-family: sans-serif;
   2946 	}
   2947 
   2948 	/* Source code view: function entry nonzero count*/
   2949 	td.coverFnHi
   2950 	{
   2951 	  text-align: right;
   2952 	  padding-left: 10px;
   2953 	  padding-right: 10px;
   2954 	  background-color: #DAE7FE;
   2955 	  font-weight: bold;
   2956 	  font-family: sans-serif;
   2957 	}
   2958 
   2959 	/* Source code view: source code format */
   2960 	pre.source
   2961 	{
   2962 	  font-family: monospace;
   2963 	  white-space: pre;
   2964 	  margin-top: 2px;
   2965 	}
   2966 	
   2967 	/* Source code view: line number format */
   2968 	span.lineNum
   2969 	{
   2970 	  background-color: #EFE383;
   2971 	}
   2972 	
   2973 	/* Source code view: format for lines which were executed */
   2974 	td.lineCov,
   2975 	span.lineCov
   2976 	{
   2977 	  background-color: #CAD7FE;
   2978 	}
   2979 	
   2980 	/* Source code view: format for Cov legend */
   2981 	span.coverLegendCov
   2982 	{
   2983 	  padding-left: 10px;
   2984 	  padding-right: 10px;
   2985 	  padding-bottom: 2px;
   2986 	  background-color: #CAD7FE;
   2987 	}
   2988 	
   2989 	/* Source code view: format for lines which were not executed */
   2990 	td.lineNoCov,
   2991 	span.lineNoCov
   2992 	{
   2993 	  background-color: #FF6230;
   2994 	}
   2995 	
   2996 	/* Source code view: format for NoCov legend */
   2997 	span.coverLegendNoCov
   2998 	{
   2999 	  padding-left: 10px;
   3000 	  padding-right: 10px;
   3001 	  padding-bottom: 2px;
   3002 	  background-color: #FF6230;
   3003 	}
   3004 	
   3005 	/* Source code view (function table): standard link - visited format */
   3006 	td.lineNoCov > a:visited,
   3007 	td.lineCov > a:visited
   3008 	{  
   3009 	  color: black;
   3010 	  text-decoration: underline;
   3011 	}  
   3012 	
   3013 	/* Source code view: format for lines which were executed only in a
   3014 	   previous version */
   3015 	span.lineDiffCov
   3016 	{
   3017 	  background-color: #B5F7AF;
   3018 	}
   3019 	
   3020 	/* Source code view: format for branches which were executed
   3021 	 * and taken */
   3022 	span.branchCov
   3023 	{
   3024 	  background-color: #CAD7FE;
   3025 	}
   3026 
   3027 	/* Source code view: format for branches which were executed
   3028 	 * but not taken */
   3029 	span.branchNoCov
   3030 	{
   3031 	  background-color: #FF6230;
   3032 	}
   3033 
   3034 	/* Source code view: format for branches which were not executed */
   3035 	span.branchNoExec
   3036 	{
   3037 	  background-color: #FF6230;
   3038 	}
   3039 
   3040 	/* Source code view: format for the source code heading line */
   3041 	pre.sourceHeading
   3042 	{
   3043 	  white-space: pre;
   3044 	  font-family: monospace;
   3045 	  font-weight: bold;
   3046 	  margin: 0px;
   3047 	}
   3048 
   3049 	/* All views: header legend value for low rate */
   3050 	td.headerValueLegL
   3051 	{
   3052 	  font-family: sans-serif;
   3053 	  text-align: center;
   3054 	  white-space: nowrap;
   3055 	  padding-left: 4px;
   3056 	  padding-right: 2px;
   3057 	  background-color: #FF0000;
   3058 	  font-size: 80%;
   3059 	}
   3060 
   3061 	/* All views: header legend value for med rate */
   3062 	td.headerValueLegM
   3063 	{
   3064 	  font-family: sans-serif;
   3065 	  text-align: center;
   3066 	  white-space: nowrap;
   3067 	  padding-left: 2px;
   3068 	  padding-right: 2px;
   3069 	  background-color: #FFEA20;
   3070 	  font-size: 80%;
   3071 	}
   3072 
   3073 	/* All views: header legend value for hi rate */
   3074 	td.headerValueLegH
   3075 	{
   3076 	  font-family: sans-serif;
   3077 	  text-align: center;
   3078 	  white-space: nowrap;
   3079 	  padding-left: 2px;
   3080 	  padding-right: 4px;
   3081 	  background-color: #A7FC9D;
   3082 	  font-size: 80%;
   3083 	}
   3084 
   3085 	/* All views except source code view: legend format for low coverage */
   3086 	span.coverLegendCovLo
   3087 	{
   3088 	  padding-left: 10px;
   3089 	  padding-right: 10px;
   3090 	  padding-top: 2px;
   3091 	  background-color: #FF0000;
   3092 	}
   3093 
   3094 	/* All views except source code view: legend format for med coverage */
   3095 	span.coverLegendCovMed
   3096 	{
   3097 	  padding-left: 10px;
   3098 	  padding-right: 10px;
   3099 	  padding-top: 2px;
   3100 	  background-color: #FFEA20;
   3101 	}
   3102 
   3103 	/* All views except source code view: legend format for hi coverage */
   3104 	span.coverLegendCovHi
   3105 	{
   3106 	  padding-left: 10px;
   3107 	  padding-right: 10px;
   3108 	  padding-top: 2px;
   3109 	  background-color: #A7FC9D;
   3110 	}
   3111 END_OF_CSS
   3112 	;
   3113 
   3114 	# *************************************************************
   3115 
   3116 
   3117 	# Remove leading tab from all lines
   3118 	$css_data =~ s/^\t//gm;
   3119 
   3120 	print(CSS_HANDLE $css_data);
   3121 
   3122 	close(CSS_HANDLE);
   3123 }
   3124 
   3125 
   3126 #
   3127 # get_bar_graph_code(base_dir, cover_found, cover_hit)
   3128 #
   3129 # Return a string containing HTML code which implements a bar graph display
   3130 # for a coverage rate of cover_hit * 100 / cover_found.
   3131 #
   3132 
   3133 sub get_bar_graph_code($$$)
   3134 {
   3135 	my $rate;
   3136 	my $alt;
   3137 	my $width;
   3138 	my $remainder;
   3139 	my $png_name;
   3140 	my $graph_code;
   3141 
   3142 	# Check number of instrumented lines
   3143 	if ($_[1] == 0) { return ""; }
   3144 
   3145 	$rate		= $_[2] * 100 / $_[1];
   3146 	$alt		= sprintf("%.1f", $rate)."%";
   3147 	$width		= sprintf("%.0f", $rate);
   3148 	$remainder	= sprintf("%d", 100-$width);
   3149 
   3150 	# Decide which .png file to use
   3151 	$png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit,
   3152 					    $hi_limit)];
   3153 
   3154 	if ($width == 0)
   3155 	{
   3156 		# Zero coverage
   3157 		$graph_code = (<<END_OF_HTML)
   3158 	        <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]snow.png" width=100 height=10 alt="$alt"></td></tr></table>
   3159 END_OF_HTML
   3160 		;
   3161 	}
   3162 	elsif ($width == 100)
   3163 	{
   3164 		# Full coverage
   3165 		$graph_code = (<<END_OF_HTML)
   3166 		<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=100 height=10 alt="$alt"></td></tr></table>
   3167 END_OF_HTML
   3168 		;
   3169 	}
   3170 	else
   3171 	{
   3172 		# Positive coverage
   3173 		$graph_code = (<<END_OF_HTML)
   3174 		<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=$width height=10 alt="$alt"><img src="$_[0]snow.png" width=$remainder height=10 alt="$alt"></td></tr></table>
   3175 END_OF_HTML
   3176 		;
   3177 	}
   3178 
   3179 	# Remove leading tabs from all lines
   3180 	$graph_code =~ s/^\t+//gm;
   3181 	chomp($graph_code);
   3182 
   3183 	return($graph_code);
   3184 }
   3185 
   3186 #
   3187 # sub classify_rate(found, hit, med_limit, high_limit)
   3188 #
   3189 # Return 0 for low rate, 1 for medium rate and 2 for hi rate.
   3190 #
   3191 
   3192 sub classify_rate($$$$)
   3193 {
   3194 	my ($found, $hit, $med, $hi) = @_;
   3195 	my $rate;
   3196 
   3197 	if ($found == 0) {
   3198 		return 2;
   3199 	}
   3200 	$rate = $hit * 100 / $found;
   3201 	if ($rate < $med) {
   3202 		return 0;
   3203 	} elsif ($rate < $hi) {
   3204 		return 1;
   3205 	}
   3206 	return 2;
   3207 }
   3208 
   3209 
   3210 #
   3211 # write_html(filehandle, html_code)
   3212 #
   3213 # Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark
   3214 # in each line of HTML_CODE.
   3215 #
   3216 
   3217 sub write_html(*$)
   3218 {
   3219 	local *HTML_HANDLE = $_[0];
   3220 	my $html_code = $_[1];
   3221 
   3222 	# Remove leading tab from all lines
   3223 	$html_code =~ s/^\t//gm;
   3224 
   3225 	print(HTML_HANDLE $html_code)
   3226 		or die("ERROR: cannot write HTML data ($!)\n");
   3227 }
   3228 
   3229 
   3230 #
   3231 # write_html_prolog(filehandle, base_dir, pagetitle)
   3232 #
   3233 # Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will
   3234 # be used as HTML page title. BASE_DIR contains a relative path which points
   3235 # to the base directory.
   3236 #
   3237 
   3238 sub write_html_prolog(*$$)
   3239 {
   3240 	my $basedir = $_[1];
   3241 	my $pagetitle = $_[2];
   3242 	my $prolog;
   3243 
   3244 	$prolog = $html_prolog;
   3245 	$prolog =~ s/\@pagetitle\@/$pagetitle/g;
   3246 	$prolog =~ s/\@basedir\@/$basedir/g;
   3247 
   3248 	write_html($_[0], $prolog);
   3249 }
   3250 
   3251 
   3252 #
   3253 # write_header_prolog(filehandle, base_dir)
   3254 #
   3255 # Write beginning of page header HTML code.
   3256 #
   3257 
   3258 sub write_header_prolog(*$)
   3259 {
   3260 	# *************************************************************
   3261 
   3262 	write_html($_[0], <<END_OF_HTML)
   3263 	  <table width="100%" border=0 cellspacing=0 cellpadding=0>
   3264 	    <tr><td class="title">$title</td></tr>
   3265 	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   3266 
   3267 	    <tr>
   3268 	      <td width="100%">
   3269 	        <table cellpadding=1 border=0 width="100%">
   3270 END_OF_HTML
   3271 	;
   3272 
   3273 	# *************************************************************
   3274 }
   3275 
   3276 
   3277 #
   3278 # write_header_line(handle, content)
   3279 #
   3280 # Write a header line with the specified table contents.
   3281 #
   3282 
   3283 sub write_header_line(*@)
   3284 {
   3285 	my ($handle, @content) = @_;
   3286 	my $entry;
   3287 
   3288 	write_html($handle, "          <tr>\n");
   3289 	foreach $entry (@content) {
   3290 		my ($width, $class, $text, $colspan) = @{$entry};
   3291 
   3292 		if (defined($width)) {
   3293 			$width = " width=\"$width\"";
   3294 		} else {
   3295 			$width = "";
   3296 		}
   3297 		if (defined($class)) {
   3298 			$class = " class=\"$class\"";
   3299 		} else {
   3300 			$class = "";
   3301 		}
   3302 		if (defined($colspan)) {
   3303 			$colspan = " colspan=\"$colspan\"";
   3304 		} else {
   3305 			$colspan = "";
   3306 		}
   3307 		$text = "" if (!defined($text));
   3308 		write_html($handle,
   3309 			   "            <td$width$class$colspan>$text</td>\n");
   3310 	}
   3311 	write_html($handle, "          </tr>\n");
   3312 }
   3313 
   3314 
   3315 #
   3316 # write_header_epilog(filehandle, base_dir)
   3317 #
   3318 # Write end of page header HTML code.
   3319 #
   3320 
   3321 sub write_header_epilog(*$)
   3322 {
   3323 	# *************************************************************
   3324 
   3325 	write_html($_[0], <<END_OF_HTML)
   3326 	          <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   3327 	        </table>
   3328 	      </td>
   3329 	    </tr>
   3330 
   3331 	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   3332 	  </table>
   3333 
   3334 END_OF_HTML
   3335 	;
   3336 
   3337 	# *************************************************************
   3338 }
   3339 
   3340 
   3341 #
   3342 # write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...))
   3343 #
   3344 # Write heading for file table.
   3345 #
   3346 
   3347 sub write_file_table_prolog(*$@)
   3348 {
   3349 	my ($handle, $file_heading, @columns) = @_;
   3350 	my $num_columns = 0;
   3351 	my $file_width;
   3352 	my $col;
   3353 	my $width;
   3354 
   3355 	$width = 20 if (scalar(@columns) == 1);
   3356 	$width = 10 if (scalar(@columns) == 2);
   3357 	$width = 8 if (scalar(@columns) > 2);
   3358 
   3359 	foreach $col (@columns) {
   3360 		my ($heading, $cols) = @{$col};
   3361 
   3362 		$num_columns += $cols;
   3363 	}
   3364 	$file_width = 100 - $num_columns * $width;
   3365 
   3366 	# Table definition
   3367 	write_html($handle, <<END_OF_HTML);
   3368 	  <center>
   3369 	  <table width="80%" cellpadding=1 cellspacing=1 border=0>
   3370 
   3371 	    <tr>
   3372 	      <td width="$file_width%"><br></td>
   3373 END_OF_HTML
   3374 	# Empty first row
   3375 	foreach $col (@columns) {
   3376 		my ($heading, $cols) = @{$col};
   3377 
   3378 		while ($cols-- > 0) {
   3379 			write_html($handle, <<END_OF_HTML);
   3380 	      <td width="$width%"></td>
   3381 END_OF_HTML
   3382 		}
   3383 	}
   3384 	# Next row
   3385 	write_html($handle, <<END_OF_HTML);
   3386 	    </tr>
   3387 
   3388 	    <tr>
   3389 	      <td class="tableHead">$file_heading</td>
   3390 END_OF_HTML
   3391 	# Heading row
   3392 	foreach $col (@columns) {
   3393 		my ($heading, $cols) = @{$col};
   3394 		my $colspan = "";
   3395 
   3396 		$colspan = " colspan=$cols" if ($cols > 1);
   3397 		write_html($handle, <<END_OF_HTML);
   3398 	      <td class="tableHead"$colspan>$heading</td>
   3399 END_OF_HTML
   3400 	}
   3401 	write_html($handle, <<END_OF_HTML);
   3402 	    </tr>
   3403 END_OF_HTML
   3404 }
   3405 
   3406 
   3407 # write_file_table_entry(handle, base_dir, filename, page_link,
   3408 #			 ([ found, hit, med_limit, hi_limit, graph ], ..)
   3409 #
   3410 # Write an entry of the file table.
   3411 #
   3412 
   3413 sub write_file_table_entry(*$$$@)
   3414 {
   3415 	my ($handle, $base_dir, $filename, $page_link, @entries) = @_;
   3416 	my $file_code;
   3417 	my $entry;
   3418 
   3419 	# Add link to source if provided
   3420 	if (defined($page_link) && $page_link ne "") {
   3421 		$file_code = "<a href=\"$page_link\">$filename</a>";
   3422 	} else {
   3423 		$file_code = $filename;
   3424 	}
   3425 
   3426 	# First column: filename
   3427 	write_html($handle, <<END_OF_HTML);
   3428 	    <tr>
   3429 	      <td class="coverFile">$file_code</td>
   3430 END_OF_HTML
   3431 	# Columns as defined
   3432 	foreach $entry (@entries) {
   3433 		my ($found, $hit, $med, $hi, $graph) = @{$entry};
   3434 		my $bar_graph;
   3435 		my $class;
   3436 		my $rate;
   3437 
   3438 		# Generate bar graph if requested
   3439 		if ($graph) {
   3440 			$bar_graph = get_bar_graph_code($base_dir, $found,
   3441 							$hit);
   3442 			write_html($handle, <<END_OF_HTML);
   3443 	      <td class="coverBar" align="center">
   3444 	        $bar_graph
   3445 	      </td>
   3446 END_OF_HTML
   3447 		}
   3448 		# Get rate color and text
   3449 		if ($found == 0) {
   3450 			$rate = "-";
   3451 			$class = "Hi";
   3452 		} else {
   3453 			$rate = sprintf("%.1f&nbsp;%%", $hit * 100 / $found);
   3454 			$class = $rate_name[classify_rate($found, $hit,
   3455 					    $med, $hi)];
   3456 		}
   3457 		write_html($handle, <<END_OF_HTML);
   3458 	      <td class="coverPer$class">$rate</td>
   3459 	      <td class="coverNum$class">$hit / $found</td>
   3460 END_OF_HTML
   3461 	}
   3462 	# End of row
   3463         write_html($handle, <<END_OF_HTML);
   3464 	    </tr>
   3465 END_OF_HTML
   3466 }
   3467 
   3468 
   3469 #
   3470 # write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...))
   3471 #
   3472 # Write entry for detail section in file table.
   3473 #
   3474 
   3475 sub write_file_table_detail_entry(*$@)
   3476 {
   3477 	my ($handle, $test, @entries) = @_;
   3478 	my $entry;
   3479 
   3480 	if ($test eq "") {
   3481 		$test = "<span style=\"font-style:italic\">&lt;unnamed&gt;</span>";
   3482 	} elsif ($test =~ /^(.*),diff$/) {
   3483 		$test = $1." (converted)";
   3484 	}
   3485 	# Testname
   3486 	write_html($handle, <<END_OF_HTML);
   3487 	    <tr>
   3488 	      <td class="testName" colspan=2>$test</td>
   3489 END_OF_HTML
   3490 	# Test data
   3491 	foreach $entry (@entries) {
   3492 		my ($found, $hit) = @{$entry};
   3493 		my $rate = "-";
   3494 
   3495 		if ($found > 0) {
   3496 			$rate = sprintf("%.1f&nbsp;%%", $hit * 100 / $found);
   3497 		}
   3498 		write_html($handle, <<END_OF_HTML);
   3499 	      <td class="testPer">$rate</td>
   3500 	      <td class="testNum">$hit&nbsp;/&nbsp;$found</td>
   3501 END_OF_HTML
   3502 	}
   3503 
   3504         write_html($handle, <<END_OF_HTML);
   3505 	    </tr>
   3506 
   3507 END_OF_HTML
   3508 
   3509 	# *************************************************************
   3510 }
   3511 
   3512 
   3513 #
   3514 # write_file_table_epilog(filehandle)
   3515 #
   3516 # Write end of file table HTML code.
   3517 #
   3518 
   3519 sub write_file_table_epilog(*)
   3520 {
   3521 	# *************************************************************
   3522 
   3523 	write_html($_[0], <<END_OF_HTML)
   3524 	  </table>
   3525 	  </center>
   3526 	  <br>
   3527 
   3528 END_OF_HTML
   3529 	;
   3530 
   3531 	# *************************************************************
   3532 }
   3533 
   3534 
   3535 #
   3536 # write_test_table_prolog(filehandle, table_heading)
   3537 #
   3538 # Write heading for test case description table.
   3539 #
   3540 
   3541 sub write_test_table_prolog(*$)
   3542 {
   3543 	# *************************************************************
   3544 
   3545 	write_html($_[0], <<END_OF_HTML)
   3546 	  <center>
   3547 	  <table width="80%" cellpadding=2 cellspacing=1 border=0>
   3548 
   3549 	    <tr>
   3550 	      <td><br></td>
   3551 	    </tr>
   3552 
   3553 	    <tr>
   3554 	      <td class="tableHead">$_[1]</td>
   3555 	    </tr>
   3556 
   3557 	    <tr>
   3558 	      <td class="testDescription">
   3559 	        <dl>
   3560 END_OF_HTML
   3561 	;
   3562 
   3563 	# *************************************************************
   3564 }
   3565 
   3566 
   3567 #
   3568 # write_test_table_entry(filehandle, test_name, test_description)
   3569 #
   3570 # Write entry for the test table.
   3571 #
   3572 
   3573 sub write_test_table_entry(*$$)
   3574 {
   3575 	# *************************************************************
   3576 
   3577 	write_html($_[0], <<END_OF_HTML)
   3578           <dt>$_[1]<a name="$_[1]">&nbsp;</a></dt>
   3579           <dd>$_[2]<br><br></dd>
   3580 END_OF_HTML
   3581 	;
   3582 
   3583 	# *************************************************************
   3584 }
   3585 
   3586 
   3587 #
   3588 # write_test_table_epilog(filehandle)
   3589 #
   3590 # Write end of test description table HTML code.
   3591 #
   3592 
   3593 sub write_test_table_epilog(*)
   3594 {
   3595 	# *************************************************************
   3596 
   3597 	write_html($_[0], <<END_OF_HTML)
   3598 	        </dl>
   3599 	      </td>
   3600 	    </tr>
   3601 	  </table>
   3602 	  </center>
   3603 	  <br>
   3604 
   3605 END_OF_HTML
   3606 	;
   3607 
   3608 	# *************************************************************
   3609 }
   3610 
   3611 
   3612 sub fmt_centered($$)
   3613 {
   3614 	my ($width, $text) = @_;
   3615 	my $w0 = length($text);
   3616 	my $w1 = int(($width - $w0) / 2);
   3617 	my $w2 = $width - $w0 - $w1;
   3618 
   3619 	return (" "x$w1).$text.(" "x$w2);
   3620 }
   3621 
   3622 
   3623 #
   3624 # write_source_prolog(filehandle)
   3625 #
   3626 # Write start of source code table.
   3627 #
   3628 
   3629 sub write_source_prolog(*)
   3630 {
   3631 	my $lineno_heading = "         ";
   3632 	my $branch_heading = "";
   3633 	my $line_heading = fmt_centered($line_field_width, "Line data");
   3634 	my $source_heading = " Source code";
   3635 
   3636 	if ($br_coverage) {
   3637 		$branch_heading = fmt_centered($br_field_width, "Branch data").
   3638 				  " ";
   3639 	}
   3640 	# *************************************************************
   3641 
   3642 	write_html($_[0], <<END_OF_HTML)
   3643 	  <table cellpadding=0 cellspacing=0 border=0>
   3644 	    <tr>
   3645 	      <td><br></td>
   3646 	    </tr>
   3647 	    <tr>
   3648 	      <td>
   3649 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre>
   3650 <pre class="source">
   3651 END_OF_HTML
   3652 	;
   3653 
   3654 	# *************************************************************
   3655 }
   3656 
   3657 
   3658 #
   3659 # get_branch_blocks(brdata)
   3660 #
   3661 # Group branches that belong to the same basic block.
   3662 #
   3663 # Returns: [block1, block2, ...]
   3664 # block:   [branch1, branch2, ...]
   3665 # branch:  [block_num, branch_num, taken_count, text_length, open, close]
   3666 #
   3667 
   3668 sub get_branch_blocks($)
   3669 {
   3670 	my ($brdata) = @_;
   3671 	my $last_block_num;
   3672 	my $block = [];
   3673 	my @blocks;
   3674 	my $i;
   3675 	my $num = br_ivec_len($brdata);
   3676 
   3677 	# Group branches
   3678 	for ($i = 0; $i < $num; $i++) {
   3679 		my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
   3680 		my $br;
   3681 
   3682 		if (defined($last_block_num) && $block_num != $last_block_num) {
   3683 			push(@blocks, $block);
   3684 			$block = [];
   3685 		}
   3686 		$br = [$block_num, $branch, $taken, 3, 0, 0];
   3687 		push(@{$block}, $br);
   3688 		$last_block_num = $block_num;
   3689 	}
   3690 	push(@blocks, $block) if (scalar(@{$block}) > 0);
   3691 
   3692 	# Add braces to first and last branch in group
   3693 	foreach $block (@blocks) {
   3694 		$block->[0]->[$BR_OPEN] = 1;
   3695 		$block->[0]->[$BR_LEN]++;
   3696 		$block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1;
   3697 		$block->[scalar(@{$block}) - 1]->[$BR_LEN]++;
   3698 	}
   3699 
   3700 	return @blocks;
   3701 }
   3702 
   3703 #
   3704 # get_block_len(block)
   3705 #
   3706 # Calculate total text length of all branches in a block of branches.
   3707 #
   3708 
   3709 sub get_block_len($)
   3710 {
   3711 	my ($block) = @_;
   3712 	my $len = 0;
   3713 	my $branch;
   3714 
   3715 	foreach $branch (@{$block}) {
   3716 		$len += $branch->[$BR_LEN];
   3717 	}
   3718 
   3719 	return $len;
   3720 }
   3721 
   3722 
   3723 #
   3724 # get_branch_html(brdata)
   3725 #
   3726 # Return a list of HTML lines which represent the specified branch coverage
   3727 # data in source code view.
   3728 #
   3729 
   3730 sub get_branch_html($)
   3731 {
   3732 	my ($brdata) = @_;
   3733 	my @blocks = get_branch_blocks($brdata);
   3734 	my $block;
   3735 	my $branch;
   3736 	my $line_len = 0;
   3737 	my $line = [];	# [branch2|" ", branch|" ", ...]
   3738 	my @lines;	# [line1, line2, ...]
   3739 	my @result;
   3740 
   3741 	# Distribute blocks to lines
   3742 	foreach $block (@blocks) {
   3743 		my $block_len = get_block_len($block);
   3744 
   3745 		# Does this block fit into the current line?
   3746 		if ($line_len + $block_len <= $br_field_width) {
   3747 			# Add it
   3748 			$line_len += $block_len;
   3749 			push(@{$line}, @{$block});
   3750 			next;
   3751 		} elsif ($block_len <= $br_field_width) {
   3752 			# It would fit if the line was empty - add it to new
   3753 			# line
   3754 			push(@lines, $line);
   3755 			$line_len = $block_len;
   3756 			$line = [ @{$block} ];
   3757 			next;
   3758 		}
   3759 		# Split the block into several lines
   3760 		foreach $branch (@{$block}) {
   3761 			if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
   3762 				# Start a new line
   3763 				if (($line_len + 1 <= $br_field_width) &&
   3764 				    scalar(@{$line}) > 0 &&
   3765 				    !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) {
   3766 					# Try to align branch symbols to be in
   3767 					# one # row
   3768 					push(@{$line}, " ");
   3769 				}
   3770 				push(@lines, $line);
   3771 				$line_len = 0;
   3772 				$line = [];
   3773 			}
   3774 			push(@{$line}, $branch);
   3775 			$line_len += $branch->[$BR_LEN];
   3776 		}
   3777 	}
   3778 	push(@lines, $line);
   3779 
   3780 	# Convert to HTML
   3781 	foreach $line (@lines) {
   3782 		my $current = "";
   3783 		my $current_len = 0;
   3784 
   3785 		foreach $branch (@$line) {
   3786 			# Skip alignment space
   3787 			if ($branch eq " ") {
   3788 				$current .= " ";
   3789 				$current_len++;
   3790 				next;
   3791 			}
   3792 
   3793 			my ($block_num, $br_num, $taken, $len, $open, $close) =
   3794 			   @{$branch};
   3795 			my $class;
   3796 			my $title;
   3797 			my $text;
   3798 
   3799 			if ($taken eq '-') {
   3800 				$class	= "branchNoExec";
   3801 				$text	= " # ";
   3802 				$title	= "Branch $br_num was not executed";
   3803 			} elsif ($taken == 0) {
   3804 				$class	= "branchNoCov";
   3805 				$text	= " - ";
   3806 				$title	= "Branch $br_num was not taken";
   3807 			} else {
   3808 				$class	= "branchCov";
   3809 				$text	= " + ";
   3810 				$title	= "Branch $br_num was taken $taken ".
   3811 					  "time";
   3812 				$title .= "s" if ($taken > 1);
   3813 			}
   3814 			$current .= "[" if ($open);
   3815 			$current .= "<span class=\"$class\" title=\"$title\">";
   3816 			$current .= $text."</span>";
   3817 			$current .= "]" if ($close);
   3818 			$current_len += $len;
   3819 		}
   3820 
   3821 		# Right-align result text
   3822 		if ($current_len < $br_field_width) {
   3823 			$current = (" "x($br_field_width - $current_len)).
   3824 				   $current;
   3825 		}
   3826 		push(@result, $current);
   3827 	}
   3828 
   3829 	return @result;
   3830 }
   3831 
   3832 
   3833 #
   3834 # format_count(count, width)
   3835 #
   3836 # Return a right-aligned representation of count that fits in width characters.
   3837 #
   3838 
   3839 sub format_count($$)
   3840 {
   3841 	my ($count, $width) = @_;
   3842 	my $result;
   3843 	my $exp;
   3844 
   3845 	$result = sprintf("%*.0f", $width, $count);
   3846 	while (length($result) > $width) {
   3847 		last if ($count < 10);
   3848 		$exp++;
   3849 		$count = int($count/10);
   3850 		$result = sprintf("%*s", $width, ">$count*10^$exp");
   3851 	}
   3852 	return $result;
   3853 }
   3854 
   3855 #
   3856 # write_source_line(filehandle, line_num, source, hit_count, converted,
   3857 #                   brdata, add_anchor)
   3858 #
   3859 # Write formatted source code line. Return a line in a format as needed
   3860 # by gen_png()
   3861 #
   3862 
   3863 sub write_source_line(*$$$$$$)
   3864 {
   3865 	my ($handle, $line, $source, $count, $converted, $brdata,
   3866 	    $add_anchor) = @_;
   3867 	my $source_format;
   3868 	my $count_format;
   3869 	my $result;
   3870 	my $anchor_start = "";
   3871 	my $anchor_end = "";
   3872 	my $count_field_width = $line_field_width - 1;
   3873 	my @br_html;
   3874 	my $html;
   3875 
   3876 	# Get branch HTML data for this line
   3877 	@br_html = get_branch_html($brdata) if ($br_coverage);
   3878 
   3879 	if (!defined($count)) {
   3880 		$result		= "";
   3881 		$source_format	= "";
   3882 		$count_format	= " "x$count_field_width;
   3883 	}
   3884 	elsif ($count == 0) {
   3885 		$result		= $count;
   3886 		$source_format	= '<span class="lineNoCov">';
   3887 		$count_format	= format_count($count, $count_field_width);
   3888 	}
   3889 	elsif ($converted && defined($highlight)) {
   3890 		$result		= "*".$count;
   3891 		$source_format	= '<span class="lineDiffCov">';
   3892 		$count_format	= format_count($count, $count_field_width);
   3893 	}
   3894 	else {
   3895 		$result		= $count;
   3896 		$source_format	= '<span class="lineCov">';
   3897 		$count_format	= format_count($count, $count_field_width);
   3898 	}
   3899 	$result .= ":".$source;
   3900 
   3901 	# Write out a line number navigation anchor every $nav_resolution
   3902 	# lines if necessary
   3903 	if ($add_anchor)
   3904 	{
   3905 		$anchor_start	= "<a name=\"$_[1]\">";
   3906 		$anchor_end	= "</a>";
   3907 	}
   3908 
   3909 
   3910 	# *************************************************************
   3911 
   3912 	$html = $anchor_start;
   3913 	$html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>";
   3914 	$html .= shift(@br_html).":" if ($br_coverage);
   3915 	$html .= "$source_format$count_format : ";
   3916 	$html .= escape_html($source);
   3917 	$html .= "</span>" if ($source_format);
   3918 	$html .= $anchor_end."\n";
   3919 
   3920 	write_html($handle, $html);
   3921 
   3922 	if ($br_coverage) {
   3923 		# Add lines for overlong branch information
   3924 		foreach (@br_html) {
   3925 			write_html($handle, "<span class=\"lineNum\">".
   3926 				   "         </span>$_\n");
   3927 		}
   3928 	}
   3929 	# *************************************************************
   3930 
   3931 	return($result);
   3932 }
   3933 
   3934 
   3935 #
   3936 # write_source_epilog(filehandle)
   3937 #
   3938 # Write end of source code table.
   3939 #
   3940 
   3941 sub write_source_epilog(*)
   3942 {
   3943 	# *************************************************************
   3944 
   3945 	write_html($_[0], <<END_OF_HTML)
   3946 	</pre>
   3947 	      </td>
   3948 	    </tr>
   3949 	  </table>
   3950 	  <br>
   3951 
   3952 END_OF_HTML
   3953 	;
   3954 
   3955 	# *************************************************************
   3956 }
   3957 
   3958 
   3959 #
   3960 # write_html_epilog(filehandle, base_dir[, break_frames])
   3961 #
   3962 # Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when
   3963 # this page is embedded in a frameset, clicking the URL link will then
   3964 # break this frameset.
   3965 #
   3966 
   3967 sub write_html_epilog(*$;$)
   3968 {
   3969 	my $basedir = $_[1];
   3970 	my $break_code = "";
   3971 	my $epilog;
   3972 
   3973 	if (defined($_[2]))
   3974 	{
   3975 		$break_code = " target=\"_parent\"";
   3976 	}
   3977 
   3978 	# *************************************************************
   3979 
   3980 	write_html($_[0], <<END_OF_HTML)
   3981 	  <table width="100%" border=0 cellspacing=0 cellpadding=0>
   3982 	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   3983 	    <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr>
   3984 	  </table>
   3985 	  <br>
   3986 END_OF_HTML
   3987 	;
   3988 
   3989 	$epilog = $html_epilog;
   3990 	$epilog =~ s/\@basedir\@/$basedir/g;
   3991 
   3992 	write_html($_[0], $epilog);
   3993 }
   3994 
   3995 
   3996 #
   3997 # write_frameset(filehandle, basedir, basename, pagetitle)
   3998 #
   3999 #
   4000 
   4001 sub write_frameset(*$$$)
   4002 {
   4003 	my $frame_width = $overview_width + 40;
   4004 
   4005 	# *************************************************************
   4006 
   4007 	write_html($_[0], <<END_OF_HTML)
   4008 	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
   4009 
   4010 	<html lang="en">
   4011 
   4012 	<head>
   4013 	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   4014 	  <title>$_[3]</title>
   4015 	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
   4016 	</head>
   4017 
   4018 	<frameset cols="$frame_width,*">
   4019 	  <frame src="$_[2].gcov.overview.$html_ext" name="overview">
   4020 	  <frame src="$_[2].gcov.$html_ext" name="source">
   4021 	  <noframes>
   4022 	    <center>Frames not supported by your browser!<br></center>
   4023 	  </noframes>
   4024 	</frameset>
   4025 
   4026 	</html>
   4027 END_OF_HTML
   4028 	;
   4029 
   4030 	# *************************************************************
   4031 }
   4032 
   4033 
   4034 #
   4035 # sub write_overview_line(filehandle, basename, line, link)
   4036 #
   4037 #
   4038 
   4039 sub write_overview_line(*$$$)
   4040 {
   4041 	my $y1 = $_[2] - 1;
   4042 	my $y2 = $y1 + $nav_resolution - 1;
   4043 	my $x2 = $overview_width - 1;
   4044 
   4045 	# *************************************************************
   4046 
   4047 	write_html($_[0], <<END_OF_HTML)
   4048 	    <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview">
   4049 END_OF_HTML
   4050 	;
   4051 
   4052 	# *************************************************************
   4053 }
   4054 
   4055 
   4056 #
   4057 # write_overview(filehandle, basedir, basename, pagetitle, lines)
   4058 #
   4059 #
   4060 
   4061 sub write_overview(*$$$$)
   4062 {
   4063 	my $index;
   4064 	my $max_line = $_[4] - 1;
   4065 	my $offset;
   4066 
   4067 	# *************************************************************
   4068 
   4069 	write_html($_[0], <<END_OF_HTML)
   4070 	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
   4071 
   4072 	<html lang="en">
   4073 
   4074 	<head>
   4075 	  <title>$_[3]</title>
   4076 	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   4077 	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
   4078 	</head>
   4079 
   4080 	<body>
   4081 	  <map name="overview">
   4082 END_OF_HTML
   4083 	;
   4084 
   4085 	# *************************************************************
   4086 
   4087 	# Make $offset the next higher multiple of $nav_resolution
   4088 	$offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution;
   4089 	$offset = sprintf("%d", $offset ) * $nav_resolution;
   4090 
   4091 	# Create image map for overview image
   4092 	for ($index = 1; $index <= $_[4]; $index += $nav_resolution)
   4093 	{
   4094 		# Enforce nav_offset
   4095 		if ($index < $offset + 1)
   4096 		{
   4097 			write_overview_line($_[0], $_[2], $index, 1);
   4098 		}
   4099 		else
   4100 		{
   4101 			write_overview_line($_[0], $_[2], $index, $index - $offset);
   4102 		}
   4103 	}
   4104 
   4105 	# *************************************************************
   4106 
   4107 	write_html($_[0], <<END_OF_HTML)
   4108 	  </map>
   4109 
   4110 	  <center>
   4111 	  <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br>
   4112 	  <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview">
   4113 	  </center>
   4114 	</body>
   4115 	</html>
   4116 END_OF_HTML
   4117 	;
   4118 
   4119 	# *************************************************************
   4120 }
   4121 
   4122 
   4123 # format_rate(found, hit)
   4124 #
   4125 # Return formatted percent string for coverage rate.
   4126 #
   4127 
   4128 sub format_rate($$)
   4129 {	
   4130 	return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %";
   4131 }
   4132 
   4133 
   4134 sub max($$)
   4135 {
   4136 	my ($a, $b) = @_;
   4137 
   4138 	return $a if ($a > $b);
   4139 	return $b;
   4140 }
   4141 
   4142 
   4143 #
   4144 # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found,
   4145 # lines_hit, funcs_found, funcs_hit, sort_type)
   4146 #
   4147 # Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4)
   4148 # corresponding to (directory view header, file view header, source view
   4149 # header, test case description header, function view header)
   4150 #
   4151 
   4152 sub write_header(*$$$$$$$$$$)
   4153 {
   4154 	local *HTML_HANDLE = $_[0];
   4155 	my $type = $_[1];
   4156 	my $trunc_name = $_[2];
   4157 	my $rel_filename = $_[3];
   4158 	my $lines_found = $_[4];
   4159 	my $lines_hit = $_[5];
   4160 	my $fn_found = $_[6];
   4161 	my $fn_hit = $_[7];
   4162 	my $br_found = $_[8];
   4163 	my $br_hit = $_[9];
   4164 	my $sort_type = $_[10];
   4165 	my $base_dir;
   4166 	my $view;
   4167 	my $test;
   4168 	my $base_name;
   4169 	my $style;
   4170 	my $rate;
   4171 	my @row_left;
   4172 	my @row_right;
   4173 	my $num_rows;
   4174 	my $i;
   4175 
   4176 	$base_name = basename($rel_filename);
   4177 
   4178 	# Prepare text for "current view" field
   4179 	if ($type == $HDR_DIR)
   4180 	{
   4181 		# Main overview
   4182 		$base_dir = "";
   4183 		$view = $overview_title;
   4184 	}
   4185 	elsif ($type == $HDR_FILE)
   4186 	{
   4187 		# Directory overview
   4188 		$base_dir = get_relative_base_path($rel_filename);
   4189 		$view = "<a href=\"$base_dir"."index.$html_ext\">".
   4190 			"$overview_title</a> - $trunc_name";
   4191 	}
   4192 	elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC)
   4193 	{
   4194 		# File view
   4195 		my $dir_name = dirname($rel_filename);
   4196 
   4197 		$base_dir = get_relative_base_path($dir_name);
   4198 		if ($frames)
   4199 		{
   4200 			# Need to break frameset when clicking any of these
   4201 			# links
   4202 			$view = "<a href=\"$base_dir"."index.$html_ext\" ".
   4203 				"target=\"_parent\">$overview_title</a> - ".
   4204 				"<a href=\"index.$html_ext\" target=\"_parent\">".
   4205 				"$dir_name</a> - $base_name";
   4206 		}
   4207 		else
   4208 		{
   4209 			$view = "<a href=\"$base_dir"."index.$html_ext\">".
   4210 				"$overview_title</a> - ".
   4211 				"<a href=\"index.$html_ext\">".
   4212 				"$dir_name</a> - $base_name";
   4213 		}
   4214 
   4215 		# Add function suffix
   4216 		if ($func_coverage) {
   4217 			$view .= "<span style=\"font-size: 80%;\">";
   4218 			if ($type == $HDR_SOURCE) {
   4219 				$view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)";
   4220 			} elsif ($type == $HDR_FUNC) {
   4221 				$view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";
   4222 			}
   4223 			$view .= "</span>";
   4224 		}
   4225 	}
   4226 	elsif ($type == $HDR_TESTDESC)
   4227 	{
   4228 		# Test description header
   4229 		$base_dir = "";
   4230 		$view = "<a href=\"$base_dir"."index.$html_ext\">".
   4231 			"$overview_title</a> - test case descriptions";
   4232 	}
   4233 
   4234 	# Prepare text for "test" field
   4235 	$test = escape_html($test_title);
   4236 
   4237 	# Append link to test description page if available
   4238 	if (%test_description && ($type != $HDR_TESTDESC))
   4239 	{
   4240 		if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC))
   4241 		{
   4242 			# Need to break frameset when clicking this link
   4243 			$test .= " ( <span style=\"font-size:80%;\">".
   4244 				 "<a href=\"$base_dir".
   4245 				 "descriptions.$html_ext\" target=\"_parent\">".
   4246 				 "view descriptions</a></span> )";
   4247 		}
   4248 		else
   4249 		{
   4250 			$test .= " ( <span style=\"font-size:80%;\">".
   4251 				 "<a href=\"$base_dir".
   4252 				 "descriptions.$html_ext\">".
   4253 				 "view descriptions</a></span> )";
   4254 		}
   4255 	}
   4256 
   4257 	# Write header
   4258 	write_header_prolog(*HTML_HANDLE, $base_dir);
   4259 
   4260 	# Left row
   4261 	push(@row_left, [[ "10%", "headerItem", "Current view:" ],
   4262 			 [ "35%", "headerValue", $view ]]);
   4263 	push(@row_left, [[undef, "headerItem", "Test:"],
   4264 			 [undef, "headerValue", $test]]);
   4265 	push(@row_left, [[undef, "headerItem", "Date:"],
   4266 			 [undef, "headerValue", $date]]);
   4267 
   4268 	# Right row
   4269 	if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) {
   4270 		my $text = <<END_OF_HTML;
   4271             Lines:
   4272             <span class="coverLegendCov">hit</span>
   4273             <span class="coverLegendNoCov">not hit</span>
   4274 END_OF_HTML
   4275 		if ($br_coverage) {
   4276 			$text .= <<END_OF_HTML;
   4277             | Branches:
   4278             <span class="coverLegendCov">+</span> taken
   4279             <span class="coverLegendNoCov">-</span> not taken
   4280             <span class="coverLegendNoCov">#</span> not executed
   4281 END_OF_HTML
   4282 		}
   4283 		push(@row_left, [[undef, "headerItem", "Legend:"],
   4284 				 [undef, "headerValueLeg", $text]]);
   4285 	} elsif ($legend && ($type != $HDR_TESTDESC)) {
   4286 		my $text = <<END_OF_HTML;
   4287 	    Rating:
   4288             <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: &lt; $med_limit %</span>
   4289             <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: &gt;= $med_limit %</span>
   4290             <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: &gt;= $hi_limit %</span>
   4291 END_OF_HTML
   4292 		push(@row_left, [[undef, "headerItem", "Legend:"],
   4293 				 [undef, "headerValueLeg", $text]]);
   4294 	}
   4295 	if ($type == $HDR_TESTDESC) {
   4296 		push(@row_right, [[ "55%" ]]);
   4297 	} else {
   4298 		push(@row_right, [["15%", undef, undef ],
   4299 				  ["10%", "headerCovTableHead", "Hit" ],
   4300 				  ["10%", "headerCovTableHead", "Total" ],
   4301 				  ["15%", "headerCovTableHead", "Coverage"]]);
   4302 	}
   4303 	# Line coverage
   4304 	$style = $rate_name[classify_rate($lines_found, $lines_hit,
   4305 					  $med_limit, $hi_limit)];
   4306 	$rate = format_rate($lines_found, $lines_hit);
   4307 	push(@row_right, [[undef, "headerItem", "Lines:"],
   4308 			  [undef, "headerCovTableEntry", $lines_hit],
   4309 			  [undef, "headerCovTableEntry", $lines_found],
   4310 			  [undef, "headerCovTableEntry$style", $rate]])
   4311 			if ($type != $HDR_TESTDESC);
   4312 	# Function coverage
   4313 	if ($func_coverage) {
   4314 		$style = $rate_name[classify_rate($fn_found, $fn_hit,
   4315 						  $fn_med_limit, $fn_hi_limit)];
   4316 		$rate = format_rate($fn_found, $fn_hit);
   4317 		push(@row_right, [[undef, "headerItem", "Functions:"],
   4318 				  [undef, "headerCovTableEntry", $fn_hit],
   4319 				  [undef, "headerCovTableEntry", $fn_found],
   4320 				  [undef, "headerCovTableEntry$style", $rate]])
   4321 			if ($type != $HDR_TESTDESC);
   4322 	}
   4323 	# Branch coverage
   4324 	if ($br_coverage) {
   4325 		$style = $rate_name[classify_rate($br_found, $br_hit,
   4326 						  $br_med_limit, $br_hi_limit)];
   4327 		$rate = format_rate($br_found, $br_hit);
   4328 		push(@row_right, [[undef, "headerItem", "Branches:"],
   4329 				  [undef, "headerCovTableEntry", $br_hit],
   4330 				  [undef, "headerCovTableEntry", $br_found],
   4331 				  [undef, "headerCovTableEntry$style", $rate]])
   4332 			if ($type != $HDR_TESTDESC);
   4333 	}
   4334 
   4335 	# Print rows
   4336 	$num_rows = max(scalar(@row_left), scalar(@row_right));
   4337 	for ($i = 0; $i < $num_rows; $i++) {
   4338 		my $left = $row_left[$i];
   4339 		my $right = $row_right[$i];
   4340 
   4341 		if (!defined($left)) {
   4342 			$left = [[undef, undef, undef], [undef, undef, undef]];
   4343 		}
   4344 		if (!defined($right)) {
   4345 			$right = [];
   4346 		}
   4347 		write_header_line(*HTML_HANDLE, @{$left},
   4348 				  [ $i == 0 ? "5%" : undef, undef, undef],
   4349 				  @{$right});
   4350 	}
   4351 
   4352 	# Fourth line
   4353 	write_header_epilog(*HTML_HANDLE, $base_dir);
   4354 }
   4355 
   4356 
   4357 #
   4358 # get_sorted_keys(hash_ref, sort_type)
   4359 #
   4360 
   4361 sub get_sorted_keys($$)
   4362 {
   4363 	my ($hash, $type) = @_;
   4364 
   4365 	if ($type == $SORT_FILE) {
   4366 		# Sort by name
   4367 		return sort(keys(%{$hash}));
   4368 	} elsif ($type == $SORT_LINE) {
   4369 		# Sort by line coverage
   4370 		return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash}));
   4371 	} elsif ($type == $SORT_FUNC) {
   4372 		# Sort by function coverage;
   4373 		return sort({$hash->{$a}[8] <=> $hash->{$b}[8]}	keys(%{$hash}));
   4374 	} elsif ($type == $SORT_BRANCH) {
   4375 		# Sort by br coverage;
   4376 		return sort({$hash->{$a}[9] <=> $hash->{$b}[9]}	keys(%{$hash}));
   4377 	}
   4378 }
   4379 
   4380 sub get_sort_code($$$)
   4381 {
   4382 	my ($link, $alt, $base) = @_;
   4383 	my $png;
   4384 	my $link_start;
   4385 	my $link_end;
   4386 
   4387 	if (!defined($link)) {
   4388 		$png = "glass.png";
   4389 		$link_start = "";
   4390 		$link_end = "";
   4391 	} else {
   4392 		$png = "updown.png";
   4393 		$link_start = '<a href="'.$link.'">';
   4394 		$link_end = "</a>";
   4395 	}
   4396 
   4397 	return ' <span class="tableHeadSort">'.$link_start.
   4398 	       '<img src="'.$base.$png.'" width=10 height=14 '.
   4399 	       'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>';
   4400 }
   4401 
   4402 sub get_file_code($$$$)
   4403 {
   4404 	my ($type, $text, $sort_button, $base) = @_;
   4405 	my $result = $text;
   4406 	my $link;
   4407 
   4408 	if ($sort_button) {
   4409 		if ($type == $HEAD_NO_DETAIL) {
   4410 			$link = "index.$html_ext";
   4411 		} else {
   4412 			$link = "index-detail.$html_ext";
   4413 		}
   4414 	}
   4415 	$result .= get_sort_code($link, "Sort by name", $base);
   4416 
   4417 	return $result;
   4418 }
   4419 
   4420 sub get_line_code($$$$$)
   4421 {
   4422 	my ($type, $sort_type, $text, $sort_button, $base) = @_;
   4423 	my $result = $text;
   4424 	my $sort_link;
   4425 
   4426 	if ($type == $HEAD_NO_DETAIL) {
   4427 		# Just text
   4428 		if ($sort_button) {
   4429 			$sort_link = "index-sort-l.$html_ext";
   4430 		}
   4431 	} elsif ($type == $HEAD_DETAIL_HIDDEN) {
   4432 		# Text + link to detail view
   4433 		$result .= ' ( <a class="detail" href="index-detail'.
   4434 			   $fileview_sortname[$sort_type].'.'.$html_ext.
   4435 			   '">show details</a> )';
   4436 		if ($sort_button) {
   4437 			$sort_link = "index-sort-l.$html_ext";
   4438 		}
   4439 	} else {
   4440 		# Text + link to standard view
   4441 		$result .= ' ( <a class="detail" href="index'.
   4442 			   $fileview_sortname[$sort_type].'.'.$html_ext.
   4443 			   '">hide details</a> )';
   4444 		if ($sort_button) {
   4445 			$sort_link = "index-detail-sort-l.$html_ext";
   4446 		}
   4447 	}
   4448 	# Add sort button
   4449 	$result .= get_sort_code($sort_link, "Sort by line coverage", $base);
   4450 
   4451 	return $result;
   4452 }
   4453 
   4454 sub get_func_code($$$$)
   4455 {
   4456 	my ($type, $text, $sort_button, $base) = @_;
   4457 	my $result = $text;
   4458 	my $link;
   4459 
   4460 	if ($sort_button) {
   4461 		if ($type == $HEAD_NO_DETAIL) {
   4462 			$link = "index-sort-f.$html_ext";
   4463 		} else {
   4464 			$link = "index-detail-sort-f.$html_ext";
   4465 		}
   4466 	}
   4467 	$result .= get_sort_code($link, "Sort by function coverage", $base);
   4468 	return $result;
   4469 }
   4470 
   4471 sub get_br_code($$$$)
   4472 {
   4473 	my ($type, $text, $sort_button, $base) = @_;
   4474 	my $result = $text;
   4475 	my $link;
   4476 
   4477 	if ($sort_button) {
   4478 		if ($type == $HEAD_NO_DETAIL) {
   4479 			$link = "index-sort-b.$html_ext";
   4480 		} else {
   4481 			$link = "index-detail-sort-b.$html_ext";
   4482 		}
   4483 	}
   4484 	$result .= get_sort_code($link, "Sort by branch coverage", $base);
   4485 	return $result;
   4486 }
   4487 
   4488 #
   4489 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash,
   4490 #                  testbrhash, fileview, sort_type)
   4491 #
   4492 # Write a complete file table. OVERVIEW is a reference to a hash containing
   4493 # the following mapping:
   4494 #
   4495 #   filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link,
   4496 #		 func_link"
   4497 #
   4498 # TESTHASH is a reference to the following hash:
   4499 #
   4500 #   filename -> \%testdata
   4501 #   %testdata: name of test affecting this file -> \%testcount
   4502 #   %testcount: line number -> execution count for a single test
   4503 #
   4504 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name"
   4505 # otherwise.
   4506 #
   4507 
   4508 sub write_file_table(*$$$$$$$)
   4509 {
   4510 	local *HTML_HANDLE = $_[0];
   4511 	my $base_dir = $_[1];
   4512 	my $overview = $_[2];
   4513 	my $testhash = $_[3];
   4514 	my $testfnchash = $_[4];
   4515 	my $testbrhash = $_[5];
   4516 	my $fileview = $_[6];
   4517 	my $sort_type = $_[7];
   4518 	my $filename;
   4519 	my $bar_graph;
   4520 	my $hit;
   4521 	my $found;
   4522 	my $fn_found;
   4523 	my $fn_hit;
   4524 	my $br_found;
   4525 	my $br_hit;
   4526 	my $page_link;
   4527 	my $testname;
   4528 	my $testdata;
   4529 	my $testfncdata;
   4530 	my $testbrdata;
   4531 	my %affecting_tests;
   4532 	my $line_code = "";
   4533 	my $func_code;
   4534 	my $br_code;
   4535 	my $file_code;
   4536 	my @head_columns;
   4537 
   4538 	# Determine HTML code for column headings
   4539 	if (($base_dir ne "") && $show_details)
   4540 	{
   4541 		my $detailed = keys(%{$testhash});
   4542 
   4543 		$file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN :
   4544 					$HEAD_NO_DETAIL,
   4545 					$fileview ? "Filename" : "Directory",
   4546 					$sort && $sort_type != $SORT_FILE,
   4547 					$base_dir);
   4548 		$line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN :
   4549 					$HEAD_DETAIL_HIDDEN,
   4550 					$sort_type,
   4551 					"Line Coverage",
   4552 					$sort && $sort_type != $SORT_LINE,
   4553 					$base_dir);
   4554 		$func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN :
   4555 					$HEAD_NO_DETAIL,
   4556 					"Functions",
   4557 					$sort && $sort_type != $SORT_FUNC,
   4558 					$base_dir);
   4559 		$br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN :
   4560 					$HEAD_NO_DETAIL,
   4561 					"Branches",
   4562 					$sort && $sort_type != $SORT_BRANCH,
   4563 					$base_dir);
   4564 	} else {
   4565 		$file_code = get_file_code($HEAD_NO_DETAIL,
   4566 					$fileview ? "Filename" : "Directory",
   4567 					$sort && $sort_type != $SORT_FILE,
   4568 					$base_dir);
   4569 		$line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage",
   4570 					$sort && $sort_type != $SORT_LINE,
   4571 					$base_dir);
   4572 		$func_code = get_func_code($HEAD_NO_DETAIL, "Functions",
   4573 					$sort && $sort_type != $SORT_FUNC,
   4574 					$base_dir);
   4575 		$br_code = get_br_code($HEAD_NO_DETAIL, "Branches",
   4576 					$sort && $sort_type != $SORT_BRANCH,
   4577 					$base_dir);
   4578 	}
   4579 	push(@head_columns, [ $line_code, 3 ]);
   4580 	push(@head_columns, [ $func_code, 2]) if ($func_coverage);
   4581 	push(@head_columns, [ $br_code, 2]) if ($br_coverage);
   4582 
   4583 	write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns);
   4584 
   4585 	foreach $filename (get_sorted_keys($overview, $sort_type))
   4586 	{
   4587 		my @columns;
   4588 		($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit,
   4589 		 $page_link) = @{$overview->{$filename}};
   4590 
   4591 		# Line coverage
   4592 		push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]);
   4593 		# Function coverage
   4594 		if ($func_coverage) {
   4595 			push(@columns, [$fn_found, $fn_hit, $fn_med_limit,
   4596 					$fn_hi_limit, 0]);
   4597 		}
   4598 		# Branch coverage
   4599 		if ($br_coverage) {
   4600 			push(@columns, [$br_found, $br_hit, $br_med_limit,
   4601 					$br_hi_limit, 0]);
   4602 		}
   4603 		write_file_table_entry(*HTML_HANDLE, $base_dir, $filename,
   4604 				       $page_link, @columns);
   4605 
   4606 		$testdata = $testhash->{$filename};
   4607 		$testfncdata = $testfnchash->{$filename};
   4608 		$testbrdata = $testbrhash->{$filename};
   4609 
   4610 		# Check whether we should write test specific coverage
   4611 		# as well
   4612 		if (!($show_details && $testdata)) { next; }
   4613 
   4614 		# Filter out those tests that actually affect this file
   4615 		%affecting_tests = %{ get_affecting_tests($testdata,
   4616 					$testfncdata, $testbrdata) };
   4617 
   4618 		# Does any of the tests affect this file at all?
   4619 		if (!%affecting_tests) { next; }
   4620 
   4621 		foreach $testname (keys(%affecting_tests))
   4622 		{
   4623 			my @results;
   4624 			($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) =
   4625 				split(",", $affecting_tests{$testname});
   4626 
   4627 			# Insert link to description of available
   4628 			if ($test_description{$testname})
   4629 			{
   4630 				$testname = "<a href=\"$base_dir".
   4631 					    "descriptions.$html_ext#$testname\">".
   4632 					    "$testname</a>";
   4633 			}
   4634 
   4635 			push(@results, [$found, $hit]);
   4636 			push(@results, [$fn_found, $fn_hit]) if ($func_coverage);
   4637 			push(@results, [$br_found, $br_hit]) if ($br_coverage);
   4638 			write_file_table_detail_entry(*HTML_HANDLE, $testname,
   4639 				@results);
   4640 		}
   4641 	}
   4642 
   4643 	write_file_table_epilog(*HTML_HANDLE);
   4644 }
   4645 
   4646 
   4647 #
   4648 # get_found_and_hit(hash)
   4649 #
   4650 # Return the count for entries (found) and entries with an execution count
   4651 # greater than zero (hit) in a hash (linenumber -> execution count) as
   4652 # a list (found, hit)
   4653 #
   4654 
   4655 sub get_found_and_hit($)
   4656 {
   4657 	my %hash = %{$_[0]};
   4658 	my $found = 0;
   4659 	my $hit = 0;
   4660 
   4661 	# Calculate sum
   4662 	$found = 0;
   4663 	$hit = 0;
   4664 			
   4665 	foreach (keys(%hash))
   4666 	{
   4667 		$found++;
   4668 		if ($hash{$_}>0) { $hit++; }
   4669 	}
   4670 
   4671 	return ($found, $hit);
   4672 }
   4673 
   4674 
   4675 #
   4676 # get_func_found_and_hit(sumfnccount)
   4677 #
   4678 # Return (f_found, f_hit) for sumfnccount
   4679 #
   4680 
   4681 sub get_func_found_and_hit($)
   4682 {
   4683 	my ($sumfnccount) = @_;
   4684 	my $function;
   4685 	my $fn_found;
   4686 	my $fn_hit;
   4687 
   4688 	$fn_found = scalar(keys(%{$sumfnccount}));
   4689 	$fn_hit = 0;
   4690 	foreach $function (keys(%{$sumfnccount})) {
   4691 		if ($sumfnccount->{$function} > 0) {
   4692 			$fn_hit++;
   4693 		}
   4694 	}
   4695 	return ($fn_found, $fn_hit);
   4696 }
   4697 
   4698 
   4699 #
   4700 # br_taken_to_num(taken)
   4701 #
   4702 # Convert a branch taken value .info format to number format.
   4703 #
   4704 
   4705 sub br_taken_to_num($)
   4706 {
   4707 	my ($taken) = @_;
   4708 
   4709 	return 0 if ($taken eq '-');
   4710 	return $taken + 1;
   4711 }
   4712 
   4713 
   4714 #
   4715 # br_num_to_taken(taken)
   4716 #
   4717 # Convert a branch taken value in number format to .info format.
   4718 #
   4719 
   4720 sub br_num_to_taken($)
   4721 {
   4722 	my ($taken) = @_;
   4723 
   4724 	return '-' if ($taken == 0);
   4725 	return $taken - 1;
   4726 }
   4727 
   4728 
   4729 #
   4730 # br_taken_add(taken1, taken2)
   4731 #
   4732 # Return the result of taken1 + taken2 for 'branch taken' values.
   4733 #
   4734 
   4735 sub br_taken_add($$)
   4736 {
   4737 	my ($t1, $t2) = @_;
   4738 
   4739 	return $t1 if (!defined($t2));
   4740 	return $t2 if (!defined($t1));
   4741 	return $t1 if ($t2 eq '-');
   4742 	return $t2 if ($t1 eq '-');
   4743 	return $t1 + $t2;
   4744 }
   4745 
   4746 
   4747 #
   4748 # br_taken_sub(taken1, taken2)
   4749 #
   4750 # Return the result of taken1 - taken2 for 'branch taken' values. Return 0
   4751 # if the result would become negative.
   4752 #
   4753 
   4754 sub br_taken_sub($$)
   4755 {
   4756 	my ($t1, $t2) = @_;
   4757 
   4758 	return $t1 if (!defined($t2));
   4759 	return undef if (!defined($t1));
   4760 	return $t1 if ($t1 eq '-');
   4761 	return $t1 if ($t2 eq '-');
   4762 	return 0 if $t2 > $t1;
   4763 	return $t1 - $t2;
   4764 }
   4765 
   4766 
   4767 #
   4768 # br_ivec_len(vector)
   4769 #
   4770 # Return the number of entries in the branch coverage vector.
   4771 #
   4772 
   4773 sub br_ivec_len($)
   4774 {
   4775 	my ($vec) = @_;
   4776 
   4777 	return 0 if (!defined($vec));
   4778 	return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
   4779 }
   4780 
   4781 
   4782 #
   4783 # br_ivec_get(vector, number)
   4784 #
   4785 # Return an entry from the branch coverage vector.
   4786 #
   4787 
   4788 sub br_ivec_get($$)
   4789 {
   4790 	my ($vec, $num) = @_;
   4791 	my $block;
   4792 	my $branch;
   4793 	my $taken;
   4794 	my $offset = $num * $BR_VEC_ENTRIES;
   4795 
   4796 	# Retrieve data from vector
   4797 	$block	= vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
   4798 	$branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
   4799 	$taken	= vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
   4800 
   4801 	# Decode taken value from an integer
   4802 	$taken = br_num_to_taken($taken);
   4803 
   4804 	return ($block, $branch, $taken);
   4805 }
   4806 
   4807 
   4808 #
   4809 # br_ivec_push(vector, block, branch, taken)
   4810 #
   4811 # Add an entry to the branch coverage vector. If an entry with the same
   4812 # branch ID already exists, add the corresponding taken values.
   4813 #
   4814 
   4815 sub br_ivec_push($$$$)
   4816 {
   4817 	my ($vec, $block, $branch, $taken) = @_;
   4818 	my $offset;
   4819 	my $num = br_ivec_len($vec);
   4820 	my $i;
   4821 
   4822 	$vec = "" if (!defined($vec));
   4823 
   4824 	# Check if branch already exists in vector
   4825 	for ($i = 0; $i < $num; $i++) {
   4826 		my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i);
   4827 
   4828 		next if ($v_block != $block || $v_branch != $branch);
   4829 
   4830 		# Add taken counts
   4831 		$taken = br_taken_add($taken, $v_taken);
   4832 		last;
   4833 	}
   4834 
   4835 	$offset = $i * $BR_VEC_ENTRIES;
   4836 	$taken = br_taken_to_num($taken);
   4837 
   4838 	# Add to vector
   4839 	vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
   4840 	vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
   4841 	vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
   4842 
   4843 	return $vec;
   4844 }
   4845 
   4846 
   4847 #
   4848 # get_br_found_and_hit(sumbrcount)
   4849 #
   4850 # Return (br_found, br_hit) for sumbrcount
   4851 #
   4852 
   4853 sub get_br_found_and_hit($)
   4854 {
   4855 	my ($sumbrcount) = @_;
   4856 	my $line;
   4857 	my $br_found = 0;
   4858 	my $br_hit = 0;
   4859 
   4860 	foreach $line (keys(%{$sumbrcount})) {
   4861 		my $brdata = $sumbrcount->{$line};
   4862 		my $i;
   4863 		my $num = br_ivec_len($brdata);
   4864 
   4865 		for ($i = 0; $i < $num; $i++) {
   4866 			my $taken;
   4867 
   4868 			(undef, undef, $taken) = br_ivec_get($brdata, $i);
   4869 
   4870 			$br_found++;
   4871 			$br_hit++ if ($taken ne "-" && $taken > 0);
   4872 		}
   4873 	}
   4874 
   4875 	return ($br_found, $br_hit);
   4876 }
   4877 
   4878 
   4879 #
   4880 # get_affecting_tests(testdata, testfncdata, testbrdata)
   4881 #
   4882 # HASHREF contains a mapping filename -> (linenumber -> exec count). Return
   4883 # a hash containing mapping filename -> "lines found, lines hit" for each
   4884 # filename which has a nonzero hit count.
   4885 #
   4886 
   4887 sub get_affecting_tests($$$)
   4888 {
   4889 	my ($testdata, $testfncdata, $testbrdata) = @_;
   4890 	my $testname;
   4891 	my $testcount;
   4892 	my $testfnccount;
   4893 	my $testbrcount;
   4894 	my %result;
   4895 	my $found;
   4896 	my $hit;
   4897 	my $fn_found;
   4898 	my $fn_hit;
   4899 	my $br_found;
   4900 	my $br_hit;
   4901 
   4902 	foreach $testname (keys(%{$testdata}))
   4903 	{
   4904 		# Get (line number -> count) hash for this test case
   4905 		$testcount = $testdata->{$testname};
   4906 		$testfnccount = $testfncdata->{$testname};
   4907 		$testbrcount = $testbrdata->{$testname};
   4908 
   4909 		# Calculate sum
   4910 		($found, $hit) = get_found_and_hit($testcount);
   4911 		($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount);
   4912 		($br_found, $br_hit) = get_br_found_and_hit($testbrcount);
   4913 
   4914 		if ($hit>0)
   4915 		{
   4916 			$result{$testname} = "$found,$hit,$fn_found,$fn_hit,".
   4917 					     "$br_found,$br_hit";
   4918 		}
   4919 	}
   4920 
   4921 	return(\%result);
   4922 }
   4923 
   4924 
   4925 sub get_hash_reverse($)
   4926 {
   4927 	my ($hash) = @_;
   4928 	my %result;
   4929 
   4930 	foreach (keys(%{$hash})) {
   4931 		$result{$hash->{$_}} = $_;
   4932 	}
   4933 
   4934 	return \%result;
   4935 }
   4936 
   4937 #
   4938 # write_source(filehandle, source_filename, count_data, checksum_data,
   4939 #              converted_data, func_data, sumbrcount)
   4940 #
   4941 # Write an HTML view of a source code file. Returns a list containing
   4942 # data as needed by gen_png().
   4943 #
   4944 # Die on error.
   4945 #
   4946 
   4947 sub write_source($$$$$$$)
   4948 {
   4949 	local *HTML_HANDLE = $_[0];
   4950 	local *SOURCE_HANDLE;
   4951 	my $source_filename = $_[1];
   4952 	my %count_data;
   4953 	my $line_number;
   4954 	my @result;
   4955 	my $checkdata = $_[3];
   4956 	my $converted = $_[4];
   4957 	my $funcdata  = $_[5];
   4958 	my $sumbrcount = $_[6];
   4959 	my $datafunc = get_hash_reverse($funcdata);
   4960 	my $add_anchor;
   4961 
   4962 	if ($_[2])
   4963 	{
   4964 		%count_data = %{$_[2]};
   4965 	}
   4966 
   4967 	open(SOURCE_HANDLE, "<".$source_filename)
   4968 		or die("ERROR: cannot open $source_filename for reading!\n");
   4969 	
   4970 	write_source_prolog(*HTML_HANDLE);
   4971 
   4972 	for ($line_number = 1; <SOURCE_HANDLE> ; $line_number++)
   4973 	{
   4974 		chomp($_);
   4975 
   4976 		# Also remove CR from line-end
   4977 		s/\015$//;
   4978 
   4979 		# Source code matches coverage data?
   4980 		if (defined($checkdata->{$line_number}) &&
   4981 		    ($checkdata->{$line_number} ne md5_base64($_)))
   4982 		{
   4983 			die("ERROR: checksum mismatch  at $source_filename:".
   4984 			    "$line_number\n");
   4985 		}
   4986 
   4987 		$add_anchor = 0;
   4988 		if ($frames) {
   4989 			if (($line_number - 1) % $nav_resolution == 0) {
   4990 				$add_anchor = 1;
   4991 			}
   4992 		}
   4993 		if ($func_coverage) {
   4994 			if ($line_number == 1) {
   4995 				$add_anchor = 1;
   4996 			} elsif (defined($datafunc->{$line_number +
   4997 						     $func_offset})) {
   4998 				$add_anchor = 1;
   4999 			}
   5000 		}
   5001 		push (@result,
   5002 		      write_source_line(HTML_HANDLE, $line_number,
   5003 					$_, $count_data{$line_number},
   5004 					$converted->{$line_number},
   5005 					$sumbrcount->{$line_number}, $add_anchor));
   5006 	}
   5007 
   5008 	close(SOURCE_HANDLE);
   5009 	write_source_epilog(*HTML_HANDLE);
   5010 	return(@result);
   5011 }
   5012 
   5013 
   5014 sub funcview_get_func_code($$$)
   5015 {
   5016 	my ($name, $base, $type) = @_;
   5017 	my $result;
   5018 	my $link;
   5019 
   5020 	if ($sort && $type == 1) {
   5021 		$link = "$name.func.$html_ext";
   5022 	}
   5023 	$result = "Function Name";
   5024 	$result .= get_sort_code($link, "Sort by function name", $base);
   5025 
   5026 	return $result;
   5027 }
   5028 
   5029 sub funcview_get_count_code($$$)
   5030 {
   5031 	my ($name, $base, $type) = @_;
   5032 	my $result;
   5033 	my $link;
   5034 
   5035 	if ($sort && $type == 0) {
   5036 		$link = "$name.func-sort-c.$html_ext";
   5037 	}
   5038 	$result = "Hit count";
   5039 	$result .= get_sort_code($link, "Sort by hit count", $base);
   5040 
   5041 	return $result;
   5042 }
   5043 
   5044 #
   5045 # funcview_get_sorted(funcdata, sumfncdata, sort_type)
   5046 #
   5047 # Depending on the value of sort_type, return a list of functions sorted
   5048 # by name (type 0) or by the associated call count (type 1).
   5049 #
   5050 
   5051 sub funcview_get_sorted($$$)
   5052 {
   5053 	my ($funcdata, $sumfncdata, $type) = @_;
   5054 
   5055 	if ($type == 0) {
   5056 		return sort(keys(%{$funcdata}));
   5057 	}
   5058 	return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}}
   5059 		    keys(%{$sumfncdata}));
   5060 }
   5061 
   5062 #
   5063 # write_function_table(filehandle, source_file, sumcount, funcdata,
   5064 #		       sumfnccount, testfncdata, sumbrcount, testbrdata,
   5065 #		       base_name, base_dir, sort_type)
   5066 #
   5067 # Write an HTML table listing all functions in a source file, including
   5068 # also function call counts and line coverages inside of each function.
   5069 #
   5070 # Die on error.
   5071 #
   5072 
   5073 sub write_function_table(*$$$$$$$$$$)
   5074 {
   5075 	local *HTML_HANDLE = $_[0];
   5076 	my $source = $_[1];
   5077 	my $sumcount = $_[2];
   5078 	my $funcdata = $_[3];
   5079 	my $sumfncdata = $_[4];
   5080 	my $testfncdata = $_[5];
   5081 	my $sumbrcount = $_[6];
   5082 	my $testbrdata = $_[7];
   5083 	my $name = $_[8];
   5084 	my $base = $_[9];
   5085 	my $type = $_[10];
   5086 	my $func;
   5087 	my $func_code;
   5088 	my $count_code;
   5089 
   5090 	# Get HTML code for headings
   5091 	$func_code = funcview_get_func_code($name, $base, $type);
   5092 	$count_code = funcview_get_count_code($name, $base, $type);
   5093 	write_html(*HTML_HANDLE, <<END_OF_HTML)
   5094 	  <center>
   5095 	  <table width="60%" cellpadding=1 cellspacing=1 border=0>
   5096 	    <tr><td><br></td></tr>
   5097 	    <tr>
   5098 	      <td width="80%" class="tableHead">$func_code</td>
   5099 	      <td width="20%" class="tableHead">$count_code</td>
   5100 	    </tr>
   5101 END_OF_HTML
   5102 	;
   5103 	
   5104 	# Get a sorted table
   5105 	foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
   5106 		if (!defined($funcdata->{$func}))
   5107 		{
   5108 			next;
   5109 		}
   5110 
   5111 		my $startline = $funcdata->{$func} - $func_offset;
   5112 		my $name = $func;
   5113 		my $count = $sumfncdata->{$name};
   5114 		my $countstyle;
   5115 
   5116 		# Demangle C++ function names if requested
   5117 		if ($demangle_cpp) {
   5118 			$name = `c++filt "$name"`;
   5119 			chomp($name);
   5120 		}
   5121 		# Escape any remaining special characters
   5122 		$name = escape_html($name);
   5123 		if ($startline < 1) {
   5124 			$startline = 1;
   5125 		}
   5126 		if ($count == 0) {
   5127 			$countstyle = "coverFnLo";
   5128 		} else {
   5129 			$countstyle = "coverFnHi";
   5130 		}
   5131 
   5132 		write_html(*HTML_HANDLE, <<END_OF_HTML)
   5133 	    <tr>
   5134               <td class="coverFn"><a href="$source#$startline">$name</a></td>
   5135               <td class="$countstyle">$count</td>
   5136             </tr>
   5137 END_OF_HTML
   5138                 ;
   5139 	}
   5140 	write_html(*HTML_HANDLE, <<END_OF_HTML)
   5141 	  </table>
   5142 	  <br>
   5143 	  </center>
   5144 END_OF_HTML
   5145 	;
   5146 }
   5147 
   5148 
   5149 #
   5150 # info(printf_parameter)
   5151 #
   5152 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
   5153 # is not set.
   5154 #
   5155 
   5156 sub info(@)
   5157 {
   5158 	if (!$quiet)
   5159 	{
   5160 		# Print info string
   5161 		printf(@_);
   5162 	}
   5163 }
   5164 
   5165 
   5166 #
   5167 # subtract_counts(data_ref, base_ref)
   5168 #
   5169 
   5170 sub subtract_counts($$)
   5171 {
   5172 	my %data = %{$_[0]};
   5173 	my %base = %{$_[1]};
   5174 	my $line;
   5175 	my $data_count;
   5176 	my $base_count;
   5177 	my $hit = 0;
   5178 	my $found = 0;
   5179 
   5180 	foreach $line (keys(%data))
   5181 	{
   5182 		$found++;
   5183 		$data_count = $data{$line};
   5184 		$base_count = $base{$line};
   5185 
   5186 		if (defined($base_count))
   5187 		{
   5188 			$data_count -= $base_count;
   5189 
   5190 			# Make sure we don't get negative numbers
   5191 			if ($data_count<0) { $data_count = 0; }
   5192 		}
   5193 
   5194 		$data{$line} = $data_count;
   5195 		if ($data_count > 0) { $hit++; }
   5196 	}
   5197 
   5198 	return (\%data, $found, $hit);
   5199 }
   5200 
   5201 
   5202 #
   5203 # subtract_fnccounts(data, base)
   5204 #
   5205 # Subtract function call counts found in base from those in data.
   5206 # Return (data, f_found, f_hit).
   5207 #
   5208 
   5209 sub subtract_fnccounts($$)
   5210 {
   5211 	my %data;
   5212 	my %base;
   5213 	my $func;
   5214 	my $data_count;
   5215 	my $base_count;
   5216 	my $fn_hit = 0;
   5217 	my $fn_found = 0;
   5218 
   5219 	%data = %{$_[0]} if (defined($_[0]));
   5220 	%base = %{$_[1]} if (defined($_[1]));
   5221 	foreach $func (keys(%data)) {
   5222 		$fn_found++;
   5223 		$data_count = $data{$func};
   5224 		$base_count = $base{$func};
   5225 
   5226 		if (defined($base_count)) {
   5227 			$data_count -= $base_count;
   5228 
   5229 			# Make sure we don't get negative numbers
   5230 			if ($data_count < 0) {
   5231 				$data_count = 0;
   5232 			}
   5233 		}
   5234 
   5235 		$data{$func} = $data_count;
   5236 		if ($data_count > 0) {
   5237 			$fn_hit++;
   5238 		}
   5239 	}
   5240 
   5241 	return (\%data, $fn_found, $fn_hit);
   5242 }
   5243 
   5244 
   5245 #
   5246 # apply_baseline(data_ref, baseline_ref)
   5247 #
   5248 # Subtract the execution counts found in the baseline hash referenced by
   5249 # BASELINE_REF from actual data in DATA_REF.
   5250 #
   5251 
   5252 sub apply_baseline($$)
   5253 {
   5254 	my %data_hash = %{$_[0]};
   5255 	my %base_hash = %{$_[1]};
   5256 	my $filename;
   5257 	my $testname;
   5258 	my $data;
   5259 	my $data_testdata;
   5260 	my $data_funcdata;
   5261 	my $data_checkdata;
   5262 	my $data_testfncdata;
   5263 	my $data_testbrdata;
   5264 	my $data_count;
   5265 	my $data_testfnccount;
   5266 	my $data_testbrcount;
   5267 	my $base;
   5268 	my $base_checkdata;
   5269 	my $base_sumfnccount;
   5270 	my $base_sumbrcount;
   5271 	my $base_count;
   5272 	my $sumcount;
   5273 	my $sumfnccount;
   5274 	my $sumbrcount;
   5275 	my $found;
   5276 	my $hit;
   5277 	my $fn_found;
   5278 	my $fn_hit;
   5279 	my $br_found;
   5280 	my $br_hit;
   5281 
   5282 	foreach $filename (keys(%data_hash))
   5283 	{
   5284 		# Get data set for data and baseline
   5285 		$data = $data_hash{$filename};
   5286 		$base = $base_hash{$filename};
   5287 
   5288 		# Skip data entries for which no base entry exists
   5289 		if (!defined($base))
   5290 		{
   5291 			next;
   5292 		}
   5293 
   5294 		# Get set entries for data and baseline
   5295 		($data_testdata, undef, $data_funcdata, $data_checkdata,
   5296 		 $data_testfncdata, undef, $data_testbrdata) =
   5297 			get_info_entry($data);
   5298 		(undef, $base_count, undef, $base_checkdata, undef,
   5299 		 $base_sumfnccount, undef, $base_sumbrcount) =
   5300 			get_info_entry($base);
   5301 
   5302 		# Check for compatible checksums
   5303 		merge_checksums($data_checkdata, $base_checkdata, $filename);
   5304 
   5305 		# sumcount has to be calculated anew
   5306 		$sumcount = {};
   5307 		$sumfnccount = {};
   5308 		$sumbrcount = {};
   5309 
   5310 		# For each test case, subtract test specific counts
   5311 		foreach $testname (keys(%{$data_testdata}))
   5312 		{
   5313 			# Get counts of both data and baseline
   5314 			$data_count = $data_testdata->{$testname};
   5315 			$data_testfnccount = $data_testfncdata->{$testname};
   5316 			$data_testbrcount = $data_testbrdata->{$testname};
   5317 
   5318 			($data_count, undef, $hit) =
   5319 				subtract_counts($data_count, $base_count);
   5320 			($data_testfnccount) =
   5321 				subtract_fnccounts($data_testfnccount,
   5322 						   $base_sumfnccount);
   5323 			($data_testbrcount) =
   5324 				combine_brcount($data_testbrcount,
   5325 						 $base_sumbrcount, $BR_SUB);
   5326 
   5327 
   5328 			# Check whether this test case did hit any line at all
   5329 			if ($hit > 0)
   5330 			{
   5331 				# Write back resulting hash
   5332 				$data_testdata->{$testname} = $data_count;
   5333 				$data_testfncdata->{$testname} =
   5334 					$data_testfnccount;
   5335 				$data_testbrdata->{$testname} =
   5336 					$data_testbrcount;
   5337 			}
   5338 			else
   5339 			{
   5340 				# Delete test case which did not impact this
   5341 				# file
   5342 				delete($data_testdata->{$testname});
   5343 				delete($data_testfncdata->{$testname});
   5344 				delete($data_testbrdata->{$testname});
   5345 			}
   5346 
   5347 			# Add counts to sum of counts
   5348 			($sumcount, $found, $hit) =
   5349 				add_counts($sumcount, $data_count);
   5350 			($sumfnccount, $fn_found, $fn_hit) =
   5351 				add_fnccount($sumfnccount, $data_testfnccount);
   5352 			($sumbrcount, $br_found, $br_hit) =
   5353 				combine_brcount($sumbrcount, $data_testbrcount,
   5354 						$BR_ADD);
   5355 		}
   5356 
   5357 		# Write back resulting entry
   5358 		set_info_entry($data, $data_testdata, $sumcount, $data_funcdata,
   5359 			       $data_checkdata, $data_testfncdata, $sumfnccount,
   5360 			       $data_testbrdata, $sumbrcount, $found, $hit,
   5361 			       $fn_found, $fn_hit, $br_found, $br_hit);
   5362 
   5363 		$data_hash{$filename} = $data;
   5364 	}
   5365 
   5366 	return (\%data_hash);
   5367 }
   5368 
   5369 
   5370 #
   5371 # remove_unused_descriptions()
   5372 #
   5373 # Removes all test descriptions from the global hash %test_description which
   5374 # are not present in %info_data.
   5375 #
   5376 
   5377 sub remove_unused_descriptions()
   5378 {
   5379 	my $filename;		# The current filename
   5380 	my %test_list;		# Hash containing found test names
   5381 	my $test_data;		# Reference to hash test_name -> count_data
   5382 	my $before;		# Initial number of descriptions
   5383 	my $after;		# Remaining number of descriptions
   5384 	
   5385 	$before = scalar(keys(%test_description));
   5386 
   5387 	foreach $filename (keys(%info_data))
   5388 	{
   5389 		($test_data) = get_info_entry($info_data{$filename});
   5390 		foreach (keys(%{$test_data}))
   5391 		{
   5392 			$test_list{$_} = "";
   5393 		}
   5394 	}
   5395 
   5396 	# Remove descriptions for tests which are not in our list
   5397 	foreach (keys(%test_description))
   5398 	{
   5399 		if (!defined($test_list{$_}))
   5400 		{
   5401 			delete($test_description{$_});
   5402 		}
   5403 	}
   5404 
   5405 	$after = scalar(keys(%test_description));
   5406 	if ($after < $before)
   5407 	{
   5408 		info("Removed ".($before - $after).
   5409 		     " unused descriptions, $after remaining.\n");
   5410 	}
   5411 }
   5412 
   5413 
   5414 #
   5415 # apply_prefix(filename, prefix)
   5416 #
   5417 # If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return
   5418 # resulting string, otherwise return FILENAME.
   5419 #
   5420 
   5421 sub apply_prefix($$)
   5422 {
   5423 	my $filename = $_[0];
   5424 	my $prefix = $_[1];
   5425 
   5426 	if (defined($prefix) && ($prefix ne ""))
   5427 	{
   5428 		if ($filename =~ /^\Q$prefix\E\/(.*)$/)
   5429 		{
   5430 			return substr($filename, length($prefix) + 1);
   5431 		}
   5432 	}
   5433 
   5434 	return $filename;
   5435 }
   5436 
   5437 
   5438 #
   5439 # system_no_output(mode, parameters)
   5440 #
   5441 # Call an external program using PARAMETERS while suppressing depending on
   5442 # the value of MODE:
   5443 #
   5444 #   MODE & 1: suppress STDOUT
   5445 #   MODE & 2: suppress STDERR
   5446 #
   5447 # Return 0 on success, non-zero otherwise.
   5448 #
   5449 
   5450 sub system_no_output($@)
   5451 {
   5452 	my $mode = shift;
   5453 	my $result;
   5454 	local *OLD_STDERR;
   5455 	local *OLD_STDOUT;
   5456 
   5457 	# Save old stdout and stderr handles
   5458 	($mode & 1) && open(OLD_STDOUT, ">>&STDOUT");
   5459 	($mode & 2) && open(OLD_STDERR, ">>&STDERR");
   5460 
   5461 	# Redirect to /dev/null
   5462 	($mode & 1) && open(STDOUT, ">/dev/null");
   5463 	($mode & 2) && open(STDERR, ">/dev/null");
   5464 
   5465 	system(@_);
   5466 	$result = $?;
   5467 
   5468 	# Close redirected handles
   5469 	($mode & 1) && close(STDOUT);
   5470 	($mode & 2) && close(STDERR);
   5471 
   5472 	# Restore old handles
   5473 	($mode & 1) && open(STDOUT, ">>&OLD_STDOUT");
   5474 	($mode & 2) && open(STDERR, ">>&OLD_STDERR");
   5475 
   5476 	return $result;
   5477 }
   5478 
   5479 
   5480 #
   5481 # read_config(filename)
   5482 #
   5483 # Read configuration file FILENAME and return a reference to a hash containing
   5484 # all valid key=value pairs found.
   5485 #
   5486 
   5487 sub read_config($)
   5488 {
   5489 	my $filename = $_[0];
   5490 	my %result;
   5491 	my $key;
   5492 	my $value;
   5493 	local *HANDLE;
   5494 
   5495 	if (!open(HANDLE, "<$filename"))
   5496 	{
   5497 		warn("WARNING: cannot read configuration file $filename\n");
   5498 		return undef;
   5499 	}
   5500 	while (<HANDLE>)
   5501 	{
   5502 		chomp;
   5503 		# Skip comments
   5504 		s/#.*//;
   5505 		# Remove leading blanks
   5506 		s/^\s+//;
   5507 		# Remove trailing blanks
   5508 		s/\s+$//;
   5509 		next unless length;
   5510 		($key, $value) = split(/\s*=\s*/, $_, 2);
   5511 		if (defined($key) && defined($value))
   5512 		{
   5513 			$result{$key} = $value;
   5514 		}
   5515 		else
   5516 		{
   5517 			warn("WARNING: malformed statement in line $. ".
   5518 			     "of configuration file $filename\n");
   5519 		}
   5520 	}
   5521 	close(HANDLE);
   5522 	return \%result;
   5523 }
   5524 
   5525 
   5526 #
   5527 # apply_config(REF)
   5528 #
   5529 # REF is a reference to a hash containing the following mapping:
   5530 #
   5531 #   key_string => var_ref
   5532 #
   5533 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
   5534 # variable. If the global configuration hash CONFIG contains a value for
   5535 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. 
   5536 #
   5537 
   5538 sub apply_config($)
   5539 {
   5540 	my $ref = $_[0];
   5541 
   5542 	foreach (keys(%{$ref}))
   5543 	{
   5544 		if (defined($config->{$_}))
   5545 		{
   5546 			${$ref->{$_}} = $config->{$_};
   5547 		}
   5548 	}
   5549 }
   5550 
   5551 
   5552 #
   5553 # get_html_prolog(FILENAME)
   5554 #
   5555 # If FILENAME is defined, return contents of file. Otherwise return default
   5556 # HTML prolog. Die on error.
   5557 #
   5558 
   5559 sub get_html_prolog($)
   5560 {
   5561 	my $filename = $_[0];
   5562 	my $result = "";
   5563 
   5564 	if (defined($filename))
   5565 	{
   5566 		local *HANDLE;
   5567 
   5568 		open(HANDLE, "<".$filename)
   5569 			or die("ERROR: cannot open html prolog $filename!\n");
   5570 		while (<HANDLE>)
   5571 		{
   5572 			$result .= $_;
   5573 		}
   5574 		close(HANDLE);
   5575 	}
   5576 	else
   5577 	{
   5578 		$result = <<END_OF_HTML
   5579 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
   5580 
   5581 <html lang="en">
   5582 
   5583 <head>
   5584   <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   5585   <title>\@pagetitle\@</title>
   5586   <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css">
   5587 </head>
   5588 
   5589 <body>
   5590 
   5591 END_OF_HTML
   5592 		;
   5593 	}
   5594 
   5595 	return $result;
   5596 }
   5597 
   5598 
   5599 #
   5600 # get_html_epilog(FILENAME)
   5601 #
   5602 # If FILENAME is defined, return contents of file. Otherwise return default
   5603 # HTML epilog. Die on error.
   5604 #
   5605 sub get_html_epilog($)
   5606 {
   5607 	my $filename = $_[0];
   5608 	my $result = "";
   5609 
   5610 	if (defined($filename))
   5611 	{
   5612 		local *HANDLE;
   5613 
   5614 		open(HANDLE, "<".$filename)
   5615 			or die("ERROR: cannot open html epilog $filename!\n");
   5616 		while (<HANDLE>)
   5617 		{
   5618 			$result .= $_;
   5619 		}
   5620 		close(HANDLE);
   5621 	}
   5622 	else
   5623 	{
   5624 		$result = <<END_OF_HTML
   5625 
   5626 </body>
   5627 </html>
   5628 END_OF_HTML
   5629 		;
   5630 	}
   5631 
   5632 	return $result;
   5633 
   5634 }
   5635 
   5636 sub warn_handler($)
   5637 {
   5638 	my ($msg) = @_;
   5639 
   5640 	warn("$tool_name: $msg");
   5641 }
   5642 
   5643 sub die_handler($)
   5644 {
   5645 	my ($msg) = @_;
   5646 
   5647 	die("$tool_name: $msg");
   5648 }
   5649