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