From 511c43aec78279ecaa6008bd7f4f08159d3a5c00 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Mon, 18 Apr 2016 18:46:39 +0200 Subject: Update 3rdParty lcov to version 1.12 The old version caused errors when running BuildTools/Coverage/GenerateCoverageResults.sh. Test-Information: Successfully ran BuildTools/Coverage/GenerateCoverageResults.sh on OS X 10.11.4. Change-Id: If6f53a85e8051388c6d5b17d980f0c3fa8e65b6e diff --git a/3rdParty/LCov/gendesc b/3rdParty/LCov/gendesc index 522ef69..7287c83 100755 --- a/3rdParty/LCov/gendesc +++ b/3rdParty/LCov/gendesc @@ -38,10 +38,12 @@ use strict; use File::Basename; use Getopt::Long; +use Cwd qw/abs_path/; # Constants -our $lcov_version = 'LCOV version 1.9'; +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.12"; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); @@ -67,9 +69,6 @@ our $input_filename; $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; - # Parse command line options if (!GetOptions("output-filename=s" => \$output_filename, "version" =>\$version, @@ -153,13 +152,13 @@ sub gen_desc() local *OUTPUT_HANDLE; my $empty_line = "ignore"; - open(INPUT_HANDLE, $input_filename) + open(INPUT_HANDLE, "<", $input_filename) or die("ERROR: cannot open $input_filename!\n"); # Open output file for writing if ($output_filename) { - open(OUTPUT_HANDLE, ">$output_filename") + open(OUTPUT_HANDLE, ">", $output_filename) or die("ERROR: cannot create $output_filename!\n"); } else diff --git a/3rdParty/LCov/genhtml b/3rdParty/LCov/genhtml index d74063a..cf1b7f5 100755 --- a/3rdParty/LCov/genhtml +++ b/3rdParty/LCov/genhtml @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) International Business Machines Corp., 2002,2010 +# Copyright (c) International Business Machines Corp., 2002,2012 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -65,17 +65,23 @@ # use strict; -use File::Basename; +use File::Basename; +use File::Temp qw(tempfile); use Getopt::Long; use Digest::MD5 qw(md5_base64); +use Cwd qw/abs_path/; # Global constants our $title = "LCOV - code coverage report"; -our $lcov_version = 'LCOV version 1.9'; +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.12"; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); +# Specify coverage rate default precision +our $default_precision = 1; + # Specify coverage rate limits (in %) for classifying file entries # HI: $hi_limit <= rate <= 100 graph color: green # MED: $med_limit <= rate < $hi_limit graph color: orange @@ -145,6 +151,7 @@ our $BR_BRANCH = 1; our $BR_TAKEN = 2; our $BR_VEC_ENTRIES = 3; our $BR_VEC_WIDTH = 32; +our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); # Additional offsets used when converting branch coverage data to HTML our $BR_LEN = 3; @@ -155,6 +162,12 @@ our $BR_CLOSE = 5; our $BR_SUB = 0; our $BR_ADD = 1; +# Error classes which users may specify to ignore during processing +our $ERROR_SOURCE = 0; +our %ERROR_ID = ( + "source" => $ERROR_SOURCE, +); + # Data related prototypes sub print_usage(*); sub gen_html(); @@ -165,7 +178,7 @@ sub info(@); sub read_info_file($); sub get_info_entry($); sub set_info_entry($$$$$$$$$;$$$$$$); -sub get_prefix(@); +sub get_prefix($@); sub shorten_prefix($); sub get_dir_list(@); sub get_relative_base_path($); @@ -181,7 +194,7 @@ sub get_affecting_tests($$$); sub combine_info_files($$); sub merge_checksums($$$); sub combine_info_entries($$$); -sub apply_prefix($$); +sub apply_prefix($@); sub system_no_output($@); sub read_config($); sub apply_config($); @@ -198,6 +211,9 @@ sub combine_brcount($$$); sub get_br_found_and_hit($); sub warn_handler($); sub die_handler($); +sub parse_ignore_errors(@); +sub parse_dir_prefix(@); +sub rate($$;$$$); # HTML related prototypes @@ -244,7 +260,8 @@ sub gen_png($$$@); # Global variables & initialization our %info_data; # Hash containing all data from .info file -our $dir_prefix; # Prefix to remove from all sub directories +our @opt_dir_prefix; # Array of prefixes to remove from all sub directories +our @dir_prefix; our %test_description; # Hash containing test descriptions if available our $date = get_date_string(); @@ -259,9 +276,9 @@ our $help; # Help option flag our $version; # Version option flag our $show_details; # If set, generate detailed directory view our $no_prefix; # If set, do not remove filename prefix -our $func_coverage = 1; # If set, generate function coverage statistics +our $func_coverage; # If set, generate function coverage statistics our $no_func_coverage; # Disable func_coverage -our $br_coverage = 1; # If set, generate branch coverage statistics +our $br_coverage; # If set, generate branch coverage statistics our $no_br_coverage; # Disable br_coverage our $sort = 1; # If set, provide directory listings with sorted entries our $no_sort; # Disable sort @@ -279,15 +296,22 @@ our $html_epilog; # Actual HTML epilog our $html_ext = "html"; # Extension for generated HTML files our $html_gzip = 0; # Compress with gzip our $demangle_cpp = 0; # Demangle C++ function names +our @opt_ignore_errors; # Ignore certain error classes during processing +our @ignore; +our $opt_config_file; # User-specified configuration file location +our %opt_rc; +our $charset = "UTF-8"; # Default charset for HTML pages our @fileview_sortlist; our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b"); our @funcview_sortlist; our @rate_name = ("Lo", "Med", "Hi"); our @rate_png = ("ruby.png", "amber.png", "emerald.png"); +our $lcov_func_coverage = 1; +our $lcov_branch_coverage = 0; +our $rc_desc_html = 0; # lcovrc: genhtml_desc_html our $cwd = `pwd`; # Current working directory chomp($cwd); -our $tool_dir = dirname($0); # Directory where genhtml tool is installed # @@ -297,17 +321,29 @@ our $tool_dir = dirname($0); # Directory where genhtml tool is installed $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; +# Check command line for a configuration file name +Getopt::Long::Configure("pass_through", "no_auto_abbrev"); +GetOptions("config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc); +Getopt::Long::Configure("default"); -# Add current working directory if $tool_dir is not already an absolute path -if (! ($tool_dir =~ /^\/(.*)$/)) { - $tool_dir = "$cwd/$tool_dir"; + # Remove spaces around rc options + my %new_opt_rc; + + while (my ($key, $value) = each(%opt_rc)) { + $key =~ s/^\s+|\s+$//g; + $value =~ s/^\s+|\s+$//g; + + $new_opt_rc{$key} = $value; + } + %opt_rc = %new_opt_rc; } # Read configuration file if available -if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +if (defined($opt_config_file)) { + $config = read_config($opt_config_file); +} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) { $config = read_config($ENV{"HOME"}."/.lcovrc"); } @@ -316,9 +352,9 @@ elsif (-r "/etc/lcovrc") $config = read_config("/etc/lcovrc"); } -if ($config) +if ($config || %opt_rc) { - # Copy configuration file values to variables + # Copy configuration file and --rc values to variables apply_config({ "genhtml_css_file" => \$css_filename, "genhtml_hi_limit" => \$hi_limit, @@ -337,6 +373,7 @@ if ($config) "genhtml_html_epilog" => \$html_epilog_file, "genhtml_html_extension" => \$html_ext, "genhtml_html_gzip" => \$html_gzip, + "genhtml_precision" => \$default_precision, "genhtml_function_hi_limit" => \$fn_hi_limit, "genhtml_function_med_limit" => \$fn_med_limit, "genhtml_function_coverage" => \$func_coverage, @@ -345,14 +382,20 @@ if ($config) "genhtml_branch_coverage" => \$br_coverage, "genhtml_branch_field_width" => \$br_field_width, "genhtml_sort" => \$sort, + "genhtml_charset" => \$charset, + "genhtml_desc_html" => \$rc_desc_html, + "lcov_function_coverage" => \$lcov_func_coverage, + "lcov_branch_coverage" => \$lcov_branch_coverage, }); } -# Copy limit values if not specified +# Copy related values if not specified $fn_hi_limit = $hi_limit if (!defined($fn_hi_limit)); $fn_med_limit = $med_limit if (!defined($fn_med_limit)); $br_hi_limit = $hi_limit if (!defined($br_hi_limit)); $br_med_limit = $med_limit if (!defined($br_med_limit)); +$func_coverage = $lcov_func_coverage if (!defined($func_coverage)); +$br_coverage = $lcov_branch_coverage if (!defined($br_coverage)); # Parse command line options if (!GetOptions("output-directory|o=s" => \$output_directory, @@ -361,7 +404,7 @@ if (!GetOptions("output-directory|o=s" => \$output_directory, "keep-descriptions|k" => \$keep_descriptions, "css-file|c=s" => \$css_filename, "baseline-file|b=s" => \$base_filename, - "prefix|p=s" => \$dir_prefix, + "prefix|p=s" => \@opt_dir_prefix, "num-spaces=i" => \$tab_size, "no-prefix" => \$no_prefix, "no-sourceview" => \$no_sourceview, @@ -383,6 +426,10 @@ if (!GetOptions("output-directory|o=s" => \$output_directory, "sort" => \$sort, "no-sort" => \$no_sort, "demangle-cpp" => \$demangle_cpp, + "ignore-errors=s" => \@opt_ignore_errors, + "config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc, + "precision=i" => \$default_precision, )) { print(STDERR "Use $tool_name --help to get usage information\n"); @@ -418,6 +465,12 @@ if ($version) exit(0); } +# Determine which errors the user wants us to ignore +parse_ignore_errors(@opt_ignore_errors); + +# Split the list of prefixes if needed +parse_dir_prefix(@opt_dir_prefix); + # Check for info filename if (!@info_filenames) { @@ -471,11 +524,11 @@ if ($no_sourceview && defined($frames)) } # Issue a warning if --no-prefix is enabled together with --prefix -if ($no_prefix && defined($dir_prefix)) +if ($no_prefix && @dir_prefix) { warn("WARNING: option --prefix disabled because --no-prefix was ". "specified!\n"); - $dir_prefix = undef; + @dir_prefix = undef; } @fileview_sortlist = ($SORT_FILE); @@ -503,6 +556,13 @@ if ($demangle_cpp) } } +# Make sure precision is within valid range +if ($default_precision < 1 || $default_precision > 4) +{ + die("ERROR: specified precision is out of range (1 to 4)\n"); +} + + # Make sure output_directory exists, create it if necessary if ($output_directory) { @@ -541,6 +601,9 @@ Misc: -h, --help Print this help, then exit -v, --version Print version number, then exit -q, --quiet Do not print progress messages + --config-file FILENAME Specify configuration file location + --rc SETTING=VALUE Override configuration file setting + --ignore-errors ERRORS Continue after ERRORS (source) Operation: -o, --output-directory OUTDIR Write HTML output to OUTDIR @@ -567,6 +630,7 @@ HTML output: --html-gzip Use gzip to compress HTML --(no-)sort Enable (disable) sorted coverage views --demangle-cpp Demangle C++ function names + --precision NUM Set precision of coverage rate For more information see: $lcov_url END_OF_USAGE @@ -607,8 +671,7 @@ sub get_overall_line($$$$) return "no data found" if (!defined($found) || $found == 0); $name = ($found == 1) ? $name_sn : $name_pl; - return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit, - $found, $name); + return rate($hit, $found, "% ($hit of $found $name)"); } @@ -636,6 +699,116 @@ sub print_overall_rate($$$$$$$$$) if ($br_do); } +sub get_fn_list($) +{ + my ($info) = @_; + my %fns; + my @result; + + foreach my $filename (keys(%{$info})) { + my $data = $info->{$filename}; + my $funcdata = $data->{"func"}; + my $sumfnccount = $data->{"sumfnc"}; + + if (defined($funcdata)) { + foreach my $func_name (keys(%{$funcdata})) { + $fns{$func_name} = 1; + } + } + + if (defined($sumfnccount)) { + foreach my $func_name (keys(%{$sumfnccount})) { + $fns{$func_name} = 1; + } + } + } + + @result = keys(%fns); + + return \@result; +} + +# +# rename_functions(info, conv) +# +# Rename all function names in INFO according to CONV: OLD_NAME -> NEW_NAME. +# In case two functions demangle to the same name, assume that they are +# different object code implementations for the same source function. +# + +sub rename_functions($$) +{ + my ($info, $conv) = @_; + + foreach my $filename (keys(%{$info})) { + my $data = $info->{$filename}; + my $funcdata; + my $testfncdata; + my $sumfnccount; + my %newfuncdata; + my %newsumfnccount; + my $f_found; + my $f_hit; + + # funcdata: function name -> line number + $funcdata = $data->{"func"}; + foreach my $fn (keys(%{$funcdata})) { + my $cn = $conv->{$fn}; + + # Abort if two functions on different lines map to the + # same demangled name. + if (defined($newfuncdata{$cn}) && + $newfuncdata{$cn} != $funcdata->{$fn}) { + die("ERROR: Demangled function name $cn ". + "maps to different lines (". + $newfuncdata{$cn}." vs ". + $funcdata->{$fn}.") in $filename\n"); + } + $newfuncdata{$cn} = $funcdata->{$fn}; + } + $data->{"func"} = \%newfuncdata; + + # testfncdata: test name -> testfnccount + # testfnccount: function name -> execution count + $testfncdata = $data->{"testfnc"}; + foreach my $tn (keys(%{$testfncdata})) { + my $testfnccount = $testfncdata->{$tn}; + my %newtestfnccount; + + foreach my $fn (keys(%{$testfnccount})) { + my $cn = $conv->{$fn}; + + # Add counts for different functions that map + # to the same name. + $newtestfnccount{$cn} += + $testfnccount->{$fn}; + } + $testfncdata->{$tn} = \%newtestfnccount; + } + + # sumfnccount: function name -> execution count + $sumfnccount = $data->{"sumfnc"}; + foreach my $fn (keys(%{$sumfnccount})) { + my $cn = $conv->{$fn}; + + # Add counts for different functions that map + # to the same name. + $newsumfnccount{$cn} += $sumfnccount->{$fn}; + } + $data->{"sumfnc"} = \%newsumfnccount; + + # Update function found and hit counts since they may have + # changed + $f_found = 0; + $f_hit = 0; + foreach my $fn (keys(%newsumfnccount)) { + $f_found++; + $f_hit++ if ($newsumfnccount{$fn} > 0); + } + $data->{"f_found"} = $f_found; + $data->{"f_hit"} = $f_hit; + } +} # # gen_html() @@ -701,14 +874,16 @@ sub gen_html() # User requested that we leave filenames alone info("User asked not to remove filename prefix\n"); } - elsif (!defined($dir_prefix)) + elsif (! @dir_prefix) { # Get prefix common to most directories in list - $dir_prefix = get_prefix(@dir_list); + my $prefix = get_prefix(1, keys(%info_data)); - if ($dir_prefix) + if ($prefix) { - info("Found common filename prefix \"$dir_prefix\"\n"); + info("Found common filename prefix \"$prefix\"\n"); + $dir_prefix[0] = $prefix; + } else { @@ -718,10 +893,17 @@ sub gen_html() } else { - info("Using user-specified filename prefix \"". - "$dir_prefix\"\n"); + my $msg = "Using user-specified filename prefix "; + for my $i (0 .. $#dir_prefix) + { + $dir_prefix[$i] =~ s/\/+$//; + $msg .= ", " unless 0 == $i; + $msg .= "\"" . $dir_prefix[$i] . "\""; + } + info($msg . "\n"); } + # Read in test description file if specified if ($desc_filename) { @@ -763,11 +945,14 @@ sub gen_html() $br_found, $br_hit) = process_dir($dir_name); + # Handle files in root directory gracefully + $dir_name = "root" if ($dir_name eq ""); + # Remove prefix if applicable - if (!$no_prefix && $dir_prefix) + if (!$no_prefix && @dir_prefix) { - # Match directory names beginning with $dir_prefix - $dir_name = apply_prefix($dir_name, $dir_prefix); + # Match directory names beginning with one of @dir_prefix + $dir_name = apply_prefix($dir_name,@dir_prefix); } # Generate name for directory overview HTML page @@ -832,13 +1017,13 @@ sub html_create($$) if ($html_gzip) { - open($handle, "|gzip -c >$filename") + open($handle, "|-", "gzip -c >'$filename'") or die("ERROR: cannot open $filename for writing ". "(gzip)!\n"); } else { - open($handle, ">$filename") + open($handle, ">", $filename) or die("ERROR: cannot open $filename for writing!\n"); } } @@ -855,6 +1040,7 @@ sub write_dir_page($$$$$$$$$$$$$$$$$) if (!defined($trunc_dir)) { $trunc_dir = ""; } + $title .= " - " if ($trunc_dir ne ""); write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir"); write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir, $overall_found, $overall_hit, $total_fn_found, @@ -904,8 +1090,8 @@ sub process_dir($) # Remove prefix if applicable if (!$no_prefix) { - # Match directory name beginning with $dir_prefix - $rel_dir = apply_prefix($rel_dir, $dir_prefix); + # Match directory name beginning with one of @dir_prefix + $rel_dir = apply_prefix($rel_dir,@dir_prefix); } $trunc_dir = $rel_dir; @@ -916,6 +1102,10 @@ sub process_dir($) $rel_dir = substr($rel_dir, 1); } + # Handle files in root directory gracefully + $rel_dir = "root" if ($rel_dir eq ""); + $trunc_dir = "root" if ($trunc_dir eq ""); + $base_dir = get_relative_base_path($rel_dir); create_sub_dir($rel_dir); @@ -1083,7 +1273,7 @@ sub write_function_page($$$$$$$$$$$$$$$$$$) sub process_file($$$) { - info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n"); + info("Processing file ".apply_prefix($_[2], @dir_prefix)."\n"); my $trunc_dir = $_[0]; my $rel_dir = $_[1]; @@ -1195,6 +1385,10 @@ sub process_file($$$) # "func" -> \%funcdata # "found" -> $lines_found (number of instrumented lines found in file) # "hit" -> $lines_hit (number of executed lines in file) +# "f_found" -> $fn_found (number of instrumented functions found in file) +# "f_hit" -> $fn_hit (number of executed functions in file) +# "b_found" -> $br_found (number of instrumented branches found in file) +# "b_hit" -> $br_hit (number of executed branches in file) # "check" -> \%checkdata # "testfnc" -> \%testfncdata # "sumfnc" -> \%sumfnccount @@ -1251,6 +1445,7 @@ sub read_info_file($) my $line_checksum; # Checksum of current line my $br_found; my $br_hit; + my $notified_about_relative_paths; local *INFO_HANDLE; # Filehandle for .info file info("Reading data file $tracefile\n"); @@ -1281,14 +1476,14 @@ sub read_info_file($) "compressed file $_[0]!\n"); # Open compressed file - open(INFO_HANDLE, "gunzip -c $_[0]|") + open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") or die("ERROR: cannot start gunzip to decompress ". "file $_[0]!\n"); } else { # Open decompressed file - open(INFO_HANDLE, $_[0]) + open(INFO_HANDLE, "<", $_[0]) or die("ERROR: cannot read file $_[0]!\n"); } @@ -1317,7 +1512,16 @@ sub read_info_file($) { # Filename information found # Retrieve data for new entry - $filename = $1; + $filename = File::Spec->rel2abs($1, Cwd::cwd()); + + if (!File::Spec->file_name_is_absolute($1) && + !$notified_about_relative_paths) + { + info("Resolved relative source file ". + "path \"$1\" with CWD to ". + "\"$filename\".\n"); + $notified_about_relative_paths = 1; + } $data = $result{$filename}; ($testdata, $sumcount, $funcdata, $checkdata, @@ -1379,6 +1583,8 @@ sub read_info_file($) /^FN:(\d+),([^,]+)/ && do { + last if (!$func_coverage); + # Function data found, add to structure $funcdata->{$2} = $1; @@ -1397,6 +1603,7 @@ sub read_info_file($) /^FNDA:(\d+),([^,]+)/ && do { + last if (!$func_coverage); # Function call count found, add to structure # Add summary counts $sumfnccount->{$2} += $1; @@ -1414,6 +1621,7 @@ sub read_info_file($) my ($line, $block, $branch, $taken) = ($1, $2, $3, $4); + last if (!$br_coverage); $sumbrcount->{$line} = br_ivec_push($sumbrcount->{$line}, $block, $branch, $taken); @@ -1614,8 +1822,8 @@ sub set_info_entry($$$$$$$$$;$$$$$$) sub add_counts($$) { - my %data1 = %{$_[0]}; # Hash 1 - my %data2 = %{$_[1]}; # Hash 2 + my $data1_ref = $_[0]; # Hash 1 + my $data2_ref = $_[1]; # Hash 2 my %result; # Resulting hash my $line; # Current line iteration scalar my $data1_count; # Count of line in hash1 @@ -1623,10 +1831,10 @@ sub add_counts($$) my $found = 0; # Total number of lines found my $hit = 0; # Number of lines with a count > 0 - foreach $line (keys(%data1)) + foreach $line (keys(%$data1_ref)) { - $data1_count = $data1{$line}; - $data2_count = $data2{$line}; + $data1_count = $data1_ref->{$line}; + $data2_count = $data2_ref->{$line}; # Add counts if present in both hashes if (defined($data2_count)) { $data1_count += $data2_count; } @@ -1638,14 +1846,14 @@ sub add_counts($$) if ($data1_count > 0) { $hit++; } } - # Add lines unique to data2 - foreach $line (keys(%data2)) + # Add lines unique to data2_ref + foreach $line (keys(%$data2_ref)) { - # Skip lines already in data1 - if (defined($data1{$line})) { next; } + # Skip lines already in data1_ref + if (defined($data1_ref->{$line})) { next; } - # Copy count from data2 - $result{$line} = $data2{$line}; + # Copy count from data2_ref + $result{$line} = $data2_ref->{$line}; $found++; if ($result{$line} > 0) { $hit++; } @@ -2110,16 +2318,17 @@ sub combine_info_files($$) # -# get_prefix(filename_list) +# get_prefix(min_dir, filename_list) # # Search FILENAME_LIST for a directory prefix which is common to as many # list entries as possible, so that removing this prefix will minimize the -# sum of the lengths of all resulting shortened filenames. +# sum of the lengths of all resulting shortened filenames while observing +# that no filename has less than MIN_DIR parent directories. # -sub get_prefix(@) +sub get_prefix($@) { - my @filename_list = @_; # provided list of filenames + my ($min_dir, @filename_list) = @_; my %prefix; # mapping: prefix -> sum of lengths my $current; # Temporary iteration variable @@ -2128,12 +2337,14 @@ sub get_prefix(@) { # Need explicit assignment to get a copy of $_ so that # shortening the contained prefix does not affect the list - $current = shorten_prefix($_); + $current = $_; while ($current = shorten_prefix($current)) { + $current .= "/"; + # Skip rest if the remaining prefix has already been # added to hash - if ($prefix{$current}) { last; } + if (exists($prefix{$current})) { last; } # Initialize with 0 $prefix{$current}="0"; @@ -2141,6 +2352,20 @@ sub get_prefix(@) } + # Remove all prefixes that would cause filenames to have less than + # the minimum number of parent directories + foreach my $filename (@filename_list) { + my $dir = dirname($filename); + + for (my $i = 0; $i < $min_dir; $i++) { + delete($prefix{$dir."/"}); + $dir = shorten_prefix($dir); + } + } + + # Check if any prefix remains + return undef if (!%prefix); + # Calculate sum of lengths for all prefixes foreach $current (keys(%prefix)) { @@ -2169,6 +2394,8 @@ sub get_prefix(@) } } + $current =~ s/\/$//; + return($current); } @@ -2260,7 +2487,7 @@ sub read_testfile($) my $changed_testname; local *TEST_HANDLE; - open(TEST_HANDLE, "<".$_[0]) + open(TEST_HANDLE, "<", $_[0]) or die("ERROR: cannot open $_[0]!\n"); while () @@ -2281,6 +2508,9 @@ sub read_testfile($) # Match lines beginning with TD: if (/^TD:\s+(.*?)\s*$/) { + if (!defined($test_name)) { + die("ERROR: Found test description without prior test name in $_[0]:$.\n"); + } # Check for empty line if ($1) { @@ -2348,10 +2578,15 @@ sub get_date_string() my $year; my $month; my $day; + my $hour; + my $min; + my $sec; - ($year, $month, $day) = (localtime())[5, 4, 3]; + ($year, $month, $day, $hour, $min, $sec) = + (localtime())[5, 4, 3, 2, 1, 0]; - return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day); + return sprintf("%d-%02d-%02d %02d:%02d:%02d", $year+1900, $month+1, + $day, $hour, $min, $sec); } @@ -2408,8 +2643,10 @@ sub write_description_file($$$$$$$) foreach $test_name (sort(keys(%description))) { - write_test_table_entry(*HTML_HANDLE, $test_name, - escape_html($description{$test_name})); + my $desc = $description{$test_name}; + + $desc = escape_html($desc) if (!$rc_desc_html); + write_test_table_entry(*HTML_HANDLE, $test_name, $desc); } write_test_table_epilog(*HTML_HANDLE); @@ -2531,7 +2768,7 @@ sub write_png_files() 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort); foreach (keys(%data)) { - open(PNG_HANDLE, ">".$_) + open(PNG_HANDLE, ">", $_) or die("ERROR: cannot create $_!\n"); binmode(PNG_HANDLE); print(PNG_HANDLE map(chr,@{$data{$_}})); @@ -2549,7 +2786,7 @@ sub write_htaccess_file() local *HTACCESS_HANDLE; my $htaccess_data; - open(*HTACCESS_HANDLE, ">.htaccess") + open(*HTACCESS_HANDLE, ">", ".htaccess") or die("ERROR: cannot open .htaccess for writing!\n"); $htaccess_data = (<<"END_OF_HTACCESS") @@ -2582,7 +2819,7 @@ sub write_css_file() return; } - open(CSS_HANDLE, ">gcov.css") + open(CSS_HANDLE, ">", "gcov.css") or die ("ERROR: cannot open gcov.css for writing!\n"); @@ -3132,6 +3369,7 @@ END_OF_CSS sub get_bar_graph_code($$$) { + my ($base_dir, $found, $hit) = @_; my $rate; my $alt; my $width; @@ -3142,13 +3380,12 @@ sub get_bar_graph_code($$$) # Check number of instrumented lines if ($_[1] == 0) { return ""; } - $rate = $_[2] * 100 / $_[1]; - $alt = sprintf("%.1f", $rate)."%"; - $width = sprintf("%.0f", $rate); - $remainder = sprintf("%d", 100-$width); + $alt = rate($hit, $found, "%"); + $width = rate($hit, $found, undef, 0); + $remainder = 100 - $width; # Decide which .png file to use - $png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit, + $png_name = $rate_png[classify_rate($found, $hit, $med_limit, $hi_limit)]; if ($width == 0) @@ -3197,7 +3434,7 @@ sub classify_rate($$$$) if ($found == 0) { return 2; } - $rate = $hit * 100 / $found; + $rate = rate($hit, $found); if ($rate < $med) { return 0; } elsif ($rate < $hi) { @@ -3415,12 +3652,13 @@ sub write_file_table_entry(*$$$@) my ($handle, $base_dir, $filename, $page_link, @entries) = @_; my $file_code; my $entry; + my $esc_filename = escape_html($filename); # Add link to source if provided if (defined($page_link) && $page_link ne "") { - $file_code = "$filename"; + $file_code = "$esc_filename"; } else { - $file_code = $filename; + $file_code = $esc_filename; } # First column: filename @@ -3450,7 +3688,7 @@ END_OF_HTML $rate = "-"; $class = "Hi"; } else { - $rate = sprintf("%.1f %%", $hit * 100 / $found); + $rate = rate($hit, $found, " %"); $class = $rate_name[classify_rate($found, $hit, $med, $hi)]; } @@ -3490,11 +3728,8 @@ END_OF_HTML # Test data foreach $entry (@entries) { my ($found, $hit) = @{$entry}; - my $rate = "-"; + my $rate = rate($hit, $found, " %"); - if ($found > 0) { - $rate = sprintf("%.1f %%", $hit * 100 / $found); - } write_html($handle, <$rate $hit / $found @@ -4010,7 +4245,7 @@ sub write_frameset(*$$$) - + $_[3] @@ -4073,7 +4308,7 @@ sub write_overview(*$$$$) $_[3] - + @@ -4120,17 +4355,6 @@ END_OF_HTML } -# format_rate(found, hit) -# -# Return formatted percent string for coverage rate. -# - -sub format_rate($$) -{ - return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %"; -} - - sub max($$) { my ($a, $b) = @_; @@ -4172,6 +4396,7 @@ sub write_header(*$$$$$$$$$$) my @row_right; my $num_rows; my $i; + my $esc_trunc_name = escape_html($trunc_name); $base_name = basename($rel_filename); @@ -4187,12 +4412,14 @@ sub write_header(*$$$$$$$$$$) # Directory overview $base_dir = get_relative_base_path($rel_filename); $view = "". - "$overview_title - $trunc_name"; + "$overview_title - $esc_trunc_name"; } elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC) { # File view my $dir_name = dirname($rel_filename); + my $esc_base_name = escape_html($base_name); + my $esc_dir_name = escape_html($dir_name); $base_dir = get_relative_base_path($dir_name); if ($frames) @@ -4202,21 +4429,25 @@ sub write_header(*$$$$$$$$$$) $view = "$overview_title - ". "". - "$dir_name - $base_name"; + "$esc_dir_name - $esc_base_name"; } else { $view = "". "$overview_title - ". "". - "$dir_name - $base_name"; + "$esc_dir_name - $esc_base_name"; } # Add function suffix if ($func_coverage) { $view .= ""; if ($type == $HDR_SOURCE) { - $view .= " (source / functions)"; + if ($sort) { + $view .= " (source / functions)"; + } else { + $view .= " (source / functions)"; + } } elsif ($type == $HDR_FUNC) { $view .= " (source / functions)"; } @@ -4303,7 +4534,7 @@ END_OF_HTML # Line coverage $style = $rate_name[classify_rate($lines_found, $lines_hit, $med_limit, $hi_limit)]; - $rate = format_rate($lines_found, $lines_hit); + $rate = rate($lines_hit, $lines_found, " %"); push(@row_right, [[undef, "headerItem", "Lines:"], [undef, "headerCovTableEntry", $lines_hit], [undef, "headerCovTableEntry", $lines_found], @@ -4313,7 +4544,7 @@ END_OF_HTML if ($func_coverage) { $style = $rate_name[classify_rate($fn_found, $fn_hit, $fn_med_limit, $fn_hi_limit)]; - $rate = format_rate($fn_found, $fn_hit); + $rate = rate($fn_hit, $fn_found, " %"); push(@row_right, [[undef, "headerItem", "Functions:"], [undef, "headerCovTableEntry", $fn_hit], [undef, "headerCovTableEntry", $fn_found], @@ -4324,7 +4555,7 @@ END_OF_HTML if ($br_coverage) { $style = $rate_name[classify_rate($br_found, $br_hit, $br_med_limit, $br_hi_limit)]; - $rate = format_rate($br_found, $br_hit); + $rate = rate($br_hit, $br_found, " %"); push(@row_right, [[undef, "headerItem", "Branches:"], [undef, "headerCovTableEntry", $br_hit], [undef, "headerCovTableEntry", $br_found], @@ -4795,6 +5026,7 @@ sub br_ivec_get($$) # Retrieve data from vector $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $block = -1 if ($block == $BR_VEC_MAX); $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); @@ -4820,10 +5052,12 @@ sub br_ivec_push($$$$) my $i; $vec = "" if (!defined($vec)); + $block = $BR_VEC_MAX if $block < 0; # Check if branch already exists in vector for ($i = 0; $i < $num; $i++) { my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); + $v_block = $BR_VEC_MAX if $v_block < 0; next if ($v_block != $block || $v_branch != $branch); @@ -4958,19 +5192,43 @@ sub write_source($$$$$$$) my $sumbrcount = $_[6]; my $datafunc = get_hash_reverse($funcdata); my $add_anchor; + my @file; if ($_[2]) { %count_data = %{$_[2]}; } - open(SOURCE_HANDLE, "<".$source_filename) - or die("ERROR: cannot open $source_filename for reading!\n"); + if (!open(SOURCE_HANDLE, "<", $source_filename)) { + my @lines; + my $last_line = 0; + + if (!$ignore[$ERROR_SOURCE]) { + die("ERROR: cannot read $source_filename\n"); + } + + # Continue without source file + warn("WARNING: cannot read $source_filename!\n"); + + @lines = sort( { $a <=> $b } keys(%count_data)); + if (@lines) { + $last_line = $lines[scalar(@lines) - 1]; + } + return ( ":" ) if ($last_line < 1); + + # Simulate gcov behavior + for ($line_number = 1; $line_number <= $last_line; + $line_number++) { + push(@file, "/* EOF */"); + } + } else { + @file = ; + } write_source_prolog(*HTML_HANDLE); - - for ($line_number = 1; ; $line_number++) - { + $line_number = 0; + foreach (@file) { + $line_number++; chomp($_); # Also remove CR from line-end @@ -5055,8 +5313,51 @@ sub funcview_get_sorted($$$) if ($type == 0) { return sort(keys(%{$funcdata})); } - return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}} - keys(%{$sumfncdata})); + return sort({ + $sumfncdata->{$b} == $sumfncdata->{$a} ? + $a cmp $b : $sumfncdata->{$a} <=> $sumfncdata->{$b} + } keys(%{$sumfncdata})); +} + +sub demangle_list($) +{ + my ($list) = @_; + my $tmpfile; + my $handle; + my %demangle; + my %versions; + + # Write function names to file + ($handle, $tmpfile) = tempfile(); + die("ERROR: could not create temporary file") if (!defined($tmpfile)); + print($handle join("\n", @$list)); + close($handle); + + # Build translation hash from c++filt output + open($handle, "-|", "c++filt < $tmpfile") or + die("ERROR: could not run c++filt: $!\n"); + foreach my $func (@$list) { + my $translated = <$handle>; + my $version; + + last if (!defined($translated)); + chomp($translated); + + $version = ++$versions{$translated}; + $translated .= ".$version" if ($version > 1); + $demangle{$func} = $translated; + } + close($handle); + + if (scalar(keys(%demangle)) != scalar(@$list)) { + die("ERROR: c++filt output not as expected (". + scalar(keys(%demangle))." vs ".scalar(@$list).") lines\n"); + } + + unlink($tmpfile) or + warn("WARNING: could not remove temporary file $tmpfile: $!\n"); + + return \%demangle; } # @@ -5086,6 +5387,7 @@ sub write_function_table(*$$$$$$$$$$) my $func; my $func_code; my $count_code; + my $demangle; # Get HTML code for headings $func_code = funcview_get_func_code($name, $base, $type); @@ -5100,7 +5402,12 @@ sub write_function_table(*$$$$$$$$$$) END_OF_HTML ; - + + # Get demangle translation hash + if ($demangle_cpp) { + $demangle = demangle_list([ sort(keys(%{$funcdata})) ]); + } + # Get a sorted table foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) { if (!defined($funcdata->{$func})) @@ -5113,12 +5420,10 @@ END_OF_HTML my $count = $sumfncdata->{$name}; my $countstyle; - # Demangle C++ function names if requested - if ($demangle_cpp) { - $name = `c++filt "$name"`; - chomp($name); - } - # Escape any remaining special characters + # Replace function name with demangled version if available + $name = $demangle->{$name} if (exists($demangle->{$name})); + + # Escape special characters $name = escape_html($name); if ($startline < 1) { $startline = 1; @@ -5412,22 +5717,25 @@ sub remove_unused_descriptions() # -# apply_prefix(filename, prefix) +# apply_prefix(filename, PREFIXES) # -# If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return -# resulting string, otherwise return FILENAME. +# If FILENAME begins with PREFIX from PREFIXES, remove PREFIX from FILENAME +# and return resulting string, otherwise return FILENAME. # -sub apply_prefix($$) +sub apply_prefix($@) { - my $filename = $_[0]; - my $prefix = $_[1]; + my $filename = shift; + my @dir_prefix = @_; - if (defined($prefix) && ($prefix ne "")) + if (@dir_prefix) { - if ($filename =~ /^\Q$prefix\E\/(.*)$/) + foreach my $prefix (@dir_prefix) { - return substr($filename, length($prefix) + 1); + if ($prefix ne "" && $filename =~ /^\Q$prefix\E\/(.*)$/) + { + return substr($filename, length($prefix) + 1); + } } } @@ -5455,12 +5763,12 @@ sub system_no_output($@) local *OLD_STDOUT; # Save old stdout and stderr handles - ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); - ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); + ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); # Redirect to /dev/null - ($mode & 1) && open(STDOUT, ">/dev/null"); - ($mode & 2) && open(STDERR, ">/dev/null"); + ($mode & 1) && open(STDOUT, ">", "/dev/null"); + ($mode & 2) && open(STDERR, ">", "/dev/null"); system(@_); $result = $?; @@ -5470,8 +5778,8 @@ sub system_no_output($@) ($mode & 2) && close(STDERR); # Restore old handles - ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); - ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); + ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); return $result; } @@ -5492,7 +5800,7 @@ sub read_config($) my $value; local *HANDLE; - if (!open(HANDLE, "<$filename")) + if (!open(HANDLE, "<", $filename)) { warn("WARNING: cannot read configuration file $filename\n"); return undef; @@ -5531,8 +5839,8 @@ sub read_config($) # key_string => var_ref # # where KEY_STRING is a keyword and VAR_REF is a reference to an associated -# variable. If the global configuration hash CONFIG contains a value for -# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# variable. If the global configuration hashes CONFIG or OPT_RC contain a value +# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. # sub apply_config($) @@ -5541,8 +5849,9 @@ sub apply_config($) foreach (keys(%{$ref})) { - if (defined($config->{$_})) - { + if (defined($opt_rc{$_})) { + ${$ref->{$_}} = $opt_rc{$_}; + } elsif (defined($config->{$_})) { ${$ref->{$_}} = $config->{$_}; } } @@ -5565,7 +5874,7 @@ sub get_html_prolog($) { local *HANDLE; - open(HANDLE, "<".$filename) + open(HANDLE, "<", $filename) or die("ERROR: cannot open html prolog $filename!\n"); while () { @@ -5581,7 +5890,7 @@ sub get_html_prolog($) - + \@pagetitle\@ @@ -5611,7 +5920,7 @@ sub get_html_epilog($) { local *HANDLE; - open(HANDLE, "<".$filename) + open(HANDLE, "<", $filename) or die("ERROR: cannot open html epilog $filename!\n"); while () { @@ -5646,3 +5955,96 @@ sub die_handler($) die("$tool_name: $msg"); } + +# +# parse_ignore_errors(@ignore_errors) +# +# Parse user input about which errors to ignore. +# + +sub parse_ignore_errors(@) +{ + my (@ignore_errors) = @_; + my @items; + my $item; + + return if (!@ignore_errors); + + foreach $item (@ignore_errors) { + $item =~ s/\s//g; + if ($item =~ /,/) { + # Split and add comma-separated parameters + push(@items, split(/,/, $item)); + } else { + # Add single parameter + push(@items, $item); + } + } + foreach $item (@items) { + my $item_id = $ERROR_ID{lc($item)}; + + if (!defined($item_id)) { + die("ERROR: unknown argument for --ignore-errors: ". + "$item\n"); + } + $ignore[$item_id] = 1; + } +} + +# +# parse_dir_prefix(@dir_prefix) +# +# Parse user input about the prefix list +# + +sub parse_dir_prefix(@) +{ + my (@opt_dir_prefix) = @_; + my $item; + + return if (!@opt_dir_prefix); + + foreach $item (@opt_dir_prefix) { + if ($item =~ /,/) { + # Split and add comma-separated parameters + push(@dir_prefix, split(/,/, $item)); + } else { + # Add single parameter + push(@dir_prefix, $item); + } + } +} + +# +# rate(hit, found[, suffix, precision, width]) +# +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. +# PRECISION specifies the precision of the result. SUFFIX defines a +# string that is appended to the result if FOUND is non-zero. Spaces +# are added to the start of the resulting string until it is at least WIDTH +# characters wide. +# + +sub rate($$;$$$) +{ + my ($hit, $found, $suffix, $precision, $width) = @_; + my $rate; + + # Assign defaults if necessary + $precision = $default_precision if (!defined($precision)); + $suffix = "" if (!defined($suffix)); + $width = 0 if (!defined($width)); + + return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); + $rate = sprintf("%.*f", $precision, $hit * 100 / $found); + + # Adjust rates if necessary + if ($rate == 0 && $hit > 0) { + $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); + } elsif ($rate == 100 && $hit != $found) { + $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); + } + + return sprintf("%*s", $width, $rate.$suffix); +} diff --git a/3rdParty/LCov/geninfo b/3rdParty/LCov/geninfo index dcb1a67..7c4e6cc 100755 --- a/3rdParty/LCov/geninfo +++ b/3rdParty/LCov/geninfo @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) International Business Machines Corp., 2002,2010 +# Copyright (c) International Business Machines Corp., 2002,2012 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,17 +52,23 @@ use strict; use File::Basename; use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir - splitpath/; + splitpath catpath/; use Getopt::Long; use Digest::MD5 qw(md5_base64); - +use Cwd qw/abs_path/; +if( $^O eq "msys" ) +{ + require File::Spec::Win32; +} # Constants -our $lcov_version = 'LCOV version 1.9'; +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.12"; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $gcov_tool = "gcov"; our $tool_name = basename($0); +our $GCOV_VERSION_4_7_0 = 0x40700; our $GCOV_VERSION_3_4_0 = 0x30400; our $GCOV_VERSION_3_3_0 = 0x30300; our $GCNO_FUNCTION_TAG = 0x01000000; @@ -70,15 +76,68 @@ our $GCNO_LINES_TAG = 0x01450000; our $GCNO_FILE_MAGIC = 0x67636e6f; our $BBG_FILE_MAGIC = 0x67626267; -our $COMPAT_HAMMER = "hammer"; - +# Error classes which users may specify to ignore during processing our $ERROR_GCOV = 0; our $ERROR_SOURCE = 1; our $ERROR_GRAPH = 2; +our %ERROR_ID = ( + "gcov" => $ERROR_GCOV, + "source" => $ERROR_SOURCE, + "graph" => $ERROR_GRAPH, +); our $EXCL_START = "LCOV_EXCL_START"; our $EXCL_STOP = "LCOV_EXCL_STOP"; -our $EXCL_LINE = "LCOV_EXCL_LINE"; + +# Marker to exclude branch coverage but keep function and line coveage +our $EXCL_BR_START = "LCOV_EXCL_BR_START"; +our $EXCL_BR_STOP = "LCOV_EXCL_BR_STOP"; + +# Compatibility mode values +our $COMPAT_VALUE_OFF = 0; +our $COMPAT_VALUE_ON = 1; +our $COMPAT_VALUE_AUTO = 2; + +# Compatibility mode value names +our %COMPAT_NAME_TO_VALUE = ( + "off" => $COMPAT_VALUE_OFF, + "on" => $COMPAT_VALUE_ON, + "auto" => $COMPAT_VALUE_AUTO, +); + +# Compatiblity modes +our $COMPAT_MODE_LIBTOOL = 1 << 0; +our $COMPAT_MODE_HAMMER = 1 << 1; +our $COMPAT_MODE_SPLIT_CRC = 1 << 2; + +# Compatibility mode names +our %COMPAT_NAME_TO_MODE = ( + "libtool" => $COMPAT_MODE_LIBTOOL, + "hammer" => $COMPAT_MODE_HAMMER, + "split_crc" => $COMPAT_MODE_SPLIT_CRC, + "android_4_4_0" => $COMPAT_MODE_SPLIT_CRC, +); + +# Map modes to names +our %COMPAT_MODE_TO_NAME = ( + $COMPAT_MODE_LIBTOOL => "libtool", + $COMPAT_MODE_HAMMER => "hammer", + $COMPAT_MODE_SPLIT_CRC => "split_crc", +); + +# Compatibility mode default values +our %COMPAT_MODE_DEFAULTS = ( + $COMPAT_MODE_LIBTOOL => $COMPAT_VALUE_ON, + $COMPAT_MODE_HAMMER => $COMPAT_VALUE_AUTO, + $COMPAT_MODE_SPLIT_CRC => $COMPAT_VALUE_AUTO, +); + +# Compatibility mode auto-detection routines +sub compat_hammer_autodetect(); +our %COMPAT_MODE_AUTO = ( + $COMPAT_MODE_HAMMER => \&compat_hammer_autodetect, + $COMPAT_MODE_SPLIT_CRC => 1, # will be done later +); our $BR_LINE = 0; our $BR_BLOCK = 1; @@ -86,8 +145,9 @@ our $BR_BRANCH = 2; our $BR_TAKEN = 3; our $BR_VEC_ENTRIES = 4; our $BR_VEC_WIDTH = 32; +our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); -our $UNNAMED_BLOCK = 9999; +our $UNNAMED_BLOCK = -1; # Prototypes sub print_usage(*); @@ -112,8 +172,9 @@ sub warn_handler($); sub die_handler($); sub graph_error($$); sub graph_expect($); -sub graph_read(*$;$); +sub graph_read(*$;$$); sub graph_skip(*$;$); +sub uniq(@); sub sort_uniq(@); sub sort_uniq_lex(@); sub graph_cleanup($); @@ -123,18 +184,19 @@ sub graph_add_order($$$); sub read_bb_word(*;$); sub read_bb_value(*;$); sub read_bb_string(*$); -sub read_bb($$); +sub read_bb($); sub read_bbg_word(*;$); sub read_bbg_value(*;$); sub read_bbg_string(*); -sub read_bbg_lines_record(*$$$$$$); -sub read_bbg($$); -sub read_gcno_word(*;$); -sub read_gcno_value(*$;$); +sub read_bbg_lines_record(*$$$$$); +sub read_bbg($); +sub read_gcno_word(*;$$); +sub read_gcno_value(*$;$$); sub read_gcno_string(*$); -sub read_gcno_lines_record(*$$$$$$$); +sub read_gcno_lines_record(*$$$$$$); +sub determine_gcno_split_crc($$$); sub read_gcno_function_record(*$$$$); -sub read_gcno($$); +sub read_gcno($); sub get_gcov_capabilities(); sub get_overall_line($$$$); sub print_overall_rate($$$$$$$$$); @@ -142,10 +204,17 @@ sub br_gvec_len($); sub br_gvec_get($$); sub debug($); sub int_handler(); +sub parse_ignore_errors(@); +sub is_external($); +sub compat_name($); +sub parse_compat_modes($); +sub is_compat($); +sub is_compat_auto($); # Global variables our $gcov_version; +our $gcov_version_string; our $graph_file_extension; our $data_file_extension; our @data_directory; @@ -158,12 +227,13 @@ our $version; our $follow; our $checksum; our $no_checksum; -our $compat_libtool; -our $no_compat_libtool; +our $opt_compat_libtool; +our $opt_no_compat_libtool; +our $rc_adjust_src_path;# Regexp specifying parts to remove from source path +our $adjust_src_pattern; +our $adjust_src_replace; our $adjust_testname; our $config; # Configuration file contents -our $compatibility; # Compatibility version flag - used to indicate - # non-standard GCOV data format versions our @ignore_errors; # List of errors to ignore (parameter) our @ignore; # List of errors to ignore (array) our $initial; @@ -171,9 +241,23 @@ our $no_recursion = 0; our $maxdepth; our $no_markers = 0; our $opt_derive_func_data = 0; +our $opt_external = 1; +our $opt_no_external; our $debug = 0; our $gcov_caps; our @gcov_options; +our @internal_dirs; +our $opt_config_file; +our $opt_gcov_all_blocks = 1; +our $opt_compat; +our %opt_rc; +our %compat_value; +our $gcno_split_crc; +our $func_coverage = 1; +our $br_coverage = 0; +our $rc_auto_base = 1; +our $excl_line = "LCOV_EXCL_LINE"; +our $excl_br_line = "LCOV_EXCL_BR_LINE"; our $cwd = `pwd`; chomp($cwd); @@ -188,14 +272,32 @@ $SIG{"INT"} = \&int_handler; $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; +# Set LC_ALL so that gcov output will be in a unified format +$ENV{"LC_ALL"} = "C"; -# Set LANG so that gcov output will be in a unified format -$ENV{"LANG"} = "C"; +# Check command line for a configuration file name +Getopt::Long::Configure("pass_through", "no_auto_abbrev"); +GetOptions("config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc); +Getopt::Long::Configure("default"); + +{ + # Remove spaces around rc options + my %new_opt_rc; + + while (my ($key, $value) = each(%opt_rc)) { + $key =~ s/^\s+|\s+$//g; + $value =~ s/^\s+|\s+$//g; + + $new_opt_rc{$key} = $value; + } + %opt_rc = %new_opt_rc; +} # Read configuration file if available -if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +if (defined($opt_config_file)) { + $config = read_config($opt_config_file); +} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) { $config = read_config($ENV{"HOME"}."/.lcovrc"); } @@ -204,15 +306,25 @@ elsif (-r "/etc/lcovrc") $config = read_config("/etc/lcovrc"); } -if ($config) +if ($config || %opt_rc) { - # Copy configuration file values to variables + # Copy configuration file and --rc values to variables apply_config({ "geninfo_gcov_tool" => \$gcov_tool, "geninfo_adjust_testname" => \$adjust_testname, "geninfo_checksum" => \$checksum, "geninfo_no_checksum" => \$no_checksum, # deprecated - "geninfo_compat_libtool" => \$compat_libtool}); + "geninfo_compat_libtool" => \$opt_compat_libtool, + "geninfo_external" => \$opt_external, + "geninfo_gcov_all_blocks" => \$opt_gcov_all_blocks, + "geninfo_compat" => \$opt_compat, + "geninfo_adjust_src_path" => \$rc_adjust_src_path, + "geninfo_auto_base" => \$rc_auto_base, + "lcov_function_coverage" => \$func_coverage, + "lcov_branch_coverage" => \$br_coverage, + "lcov_excl_line" => \$excl_line, + "lcov_excl_br_line" => \$excl_br_line, + }); # Merge options if (defined($no_checksum)) @@ -220,6 +332,34 @@ if ($config) $checksum = ($no_checksum ? 0 : 1); $no_checksum = undef; } + + # Check regexp + if (defined($rc_adjust_src_path)) { + my ($pattern, $replace) = split(/\s*=>\s*/, + $rc_adjust_src_path); + local $SIG{__DIE__}; + eval '$adjust_src_pattern = qr>'.$pattern.'>;'; + if (!defined($adjust_src_pattern)) { + my $msg = $@; + + chomp($msg); + $msg =~ s/at \(eval.*$//; + warn("WARNING: invalid pattern in ". + "geninfo_adjust_src_path: $msg\n"); + } elsif (!defined($replace)) { + # If no replacement is specified, simply remove pattern + $adjust_src_replace = ""; + } else { + $adjust_src_replace = $replace; + } + } + for my $regexp (($excl_line, $excl_br_line)) { + eval 'qr/'.$regexp.'/'; + my $error = $@; + chomp($error); + $error =~ s/at \(eval.*$//; + die("ERROR: invalid exclude pattern: $error") if $error; + } } # Parse command line options @@ -232,8 +372,8 @@ if (!GetOptions("test-name|t=s" => \$test_name, "quiet|q" => \$quiet, "help|h|?" => \$help, "follow|f" => \$follow, - "compat-libtool" => \$compat_libtool, - "no-compat-libtool" => \$no_compat_libtool, + "compat-libtool" => \$opt_compat_libtool, + "no-compat-libtool" => \$opt_no_compat_libtool, "gcov-tool=s" => \$gcov_tool, "ignore-errors=s" => \@ignore_errors, "initial|i" => \$initial, @@ -241,6 +381,11 @@ if (!GetOptions("test-name|t=s" => \$test_name, "no-markers" => \$no_markers, "derive-func-data" => \$opt_derive_func_data, "debug" => \$debug, + "external" => \$opt_external, + "no-external" => \$opt_no_external, + "compat=s" => \$opt_compat, + "config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc, )) { print(STDERR "Use $tool_name --help to get usage information\n"); @@ -255,10 +400,15 @@ else $no_checksum = undef; } - if (defined($no_compat_libtool)) + if (defined($opt_no_compat_libtool)) { - $compat_libtool = ($no_compat_libtool ? 0 : 1); - $no_compat_libtool = undef; + $opt_compat_libtool = ($opt_no_compat_libtool ? 0 : 1); + $opt_no_compat_libtool = undef; + } + + if (defined($opt_no_external)) { + $opt_external = 0; + $opt_no_external = undef; } } @@ -278,6 +428,30 @@ if ($version) exit(0); } +# Check gcov tool +if (system_no_output(3, $gcov_tool, "--help") == -1) +{ + die("ERROR: need tool $gcov_tool!\n"); +} + +($gcov_version, $gcov_version_string) = get_gcov_version(); + +# Determine gcov options +$gcov_caps = get_gcov_capabilities(); +push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'} && + ($br_coverage || $func_coverage)); +push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'} && + $br_coverage); +push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'} && + $opt_gcov_all_blocks && $br_coverage); +push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'}); + +# Determine compatibility modes +parse_compat_modes($opt_compat); + +# Determine which errors the user wants us to ignore +parse_ignore_errors(@ignore_errors); + # Make sure test names only contain valid characters if ($test_name =~ s/\W/_/g) { @@ -319,17 +493,6 @@ else $checksum = 0; } -# Determine libtool compatibility mode -if (defined($compat_libtool)) -{ - $compat_libtool = ($compat_libtool? 1 : 0); -} -else -{ - # Default is on - $compat_libtool = 1; -} - # Determine max depth for recursion if ($no_recursion) { @@ -358,42 +521,9 @@ else } } -if (@ignore_errors) -{ - my @expanded; - my $error; - - # Expand comma-separated entries - foreach (@ignore_errors) { - if (/,/) - { - push(@expanded, split(",", $_)); - } - else - { - push(@expanded, $_); - } - } - - foreach (@expanded) - { - /^gcov$/ && do { $ignore[$ERROR_GCOV] = 1; next; } ; - /^source$/ && do { $ignore[$ERROR_SOURCE] = 1; next; }; - /^graph$/ && do { $ignore[$ERROR_GRAPH] = 1; next; }; - die("ERROR: unknown argument for --ignore-errors: $_\n"); - } -} - -if (system_no_output(3, $gcov_tool, "--help") == -1) -{ - die("ERROR: need tool $gcov_tool!\n"); -} - -$gcov_version = get_gcov_version(); - if ($gcov_version < $GCOV_VERSION_3_4_0) { - if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + if (is_compat($COMPAT_MODE_HAMMER)) { $data_file_extension = ".da"; $graph_file_extension = ".bbg"; @@ -410,20 +540,13 @@ else $graph_file_extension = ".gcno"; } -# Determine gcov options -$gcov_caps = get_gcov_capabilities(); -push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'}); -push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'}); -push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'}); -push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'}); - # Check output filename if (defined($output_filename) && ($output_filename ne "-")) { # Initially create output filename, data is appended # for each data file processed local *DUMMY_HANDLE; - open(DUMMY_HANDLE, ">$output_filename") + open(DUMMY_HANDLE, ">", $output_filename) or die("ERROR: cannot create $output_filename!\n"); close(DUMMY_HANDLE); @@ -435,12 +558,18 @@ if (defined($output_filename) && ($output_filename ne "-")) } } +# Build list of directories to identify external files +foreach my $entry(@data_directory, $base_directory) { + next if (!defined($entry)); + push(@internal_dirs, solve_relative_path($cwd, $entry)); +} + # Do something foreach my $entry (@data_directory) { gen_info($entry); } -if ($initial) { +if ($initial && $br_coverage) { warn("Note: --initial does not generate branch coverage ". "data\n"); } @@ -480,9 +609,12 @@ sequentially. --gcov-tool TOOL Specify gcov tool location --ignore-errors ERROR Continue after ERROR (gcov, source, graph) --no-recursion Exclude subdirectories from processing - --function-coverage Capture function call counts --no-markers Ignore exclusion markers in source code --derive-func-data Generate function data from line data + --(no-)external Include (ignore) data for external files + --config-file FILENAME Specify configuration file location + --rc SETTING=VALUE Override configuration file setting + --compat MODE=on|off|auto Set compat MODE (libtool, hammer, split_crc) For more information see: $lcov_url END_OF_USAGE @@ -578,10 +710,13 @@ sub gen_info($) { info("Scanning $directory for $ext files ...\n"); - @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`; + @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f -o -name \\*$ext -type l 2>/dev/null`; chomp(@file_list); - @file_list or - die("ERROR: no $ext files found in $directory!\n"); + if (!@file_list) { + warn("WARNING: no $ext files found in $directory - ". + "skipping!\n"); + return; + } $prefix = get_common_prefix(1, @file_list); info("Found %d %s files in %s\n", $#file_list+1, $type, $directory); @@ -604,6 +739,25 @@ sub gen_info($) } +# +# derive_data(contentdata, funcdata, bbdata) +# +# Calculate function coverage data by combining line coverage data and the +# list of lines belonging to a function. +# +# contentdata: [ instr1, count1, source1, instr2, count2, source2, ... ] +# instr: Instrumentation flag for line n +# count: Execution count for line n +# source: Source code for line n +# +# funcdata: [ count1, func1, count2, func2, ... ] +# count: Execution count for function number n +# func: Function name for function number n +# +# bbdata: function_name -> [ line1, line2, ... ] +# line: Line number belonging to the corresponding function +# + sub derive_data($$$) { my ($contentdata, $funcdata, $bbdata) = @_; @@ -633,6 +787,7 @@ sub derive_data($$$) foreach $fn (keys(%{$bbdata})) { my $line_data = $bbdata->{$fn}; my $line; + my $fninstr = 0; if ($fn eq "") { next; @@ -640,13 +795,17 @@ sub derive_data($$$) # Find the lowest line count for this function $count = 0; foreach $line (@$line_data) { + my $linstr = $gcov_content[ ( $line - 1 ) * 3 + 0 ]; my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ]; + next if (!$linstr); + $fninstr = 1; if (($lcount > 0) && (($count == 0) || ($lcount < $count))) { $count = $lcount; } } + next if (!$fninstr); $fn_count{$fn} = $count; } @@ -733,7 +892,6 @@ sub process_dafile($$) my $source; # gcov source header information my $object; # gcov object header information my @matches; # List of absolute paths matching filename - my @unprocessed; # List of unprocessed source code files my $base_dir; # Base directory for current file my @tmp_links; # Temporary links to be cleaned up my @result; @@ -749,11 +907,10 @@ sub process_dafile($$) # Get directory and basename of data file ($da_dir, $da_basename) = split_filename($da_filename); - # avoid files from .libs dirs - if ($compat_libtool && $da_dir =~ m/(.*)\/\.libs$/) { - $source_dir = $1; - } else { - $source_dir = $da_dir; + $source_dir = $da_dir; + if (is_compat($COMPAT_MODE_LIBTOOL)) { + # Avoid files from .libs dirs + $source_dir =~ s/\.libs$//; } if (-z $da_filename) @@ -808,20 +965,27 @@ sub process_dafile($$) # information about functions and their source code positions. if ($gcov_version < $GCOV_VERSION_3_4_0) { - if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + if (is_compat($COMPAT_MODE_HAMMER)) { - ($instr, $graph) = read_bbg($bb_filename, $base_dir); + ($instr, $graph) = read_bbg($bb_filename); } else { - ($instr, $graph) = read_bb($bb_filename, $base_dir); + ($instr, $graph) = read_bb($bb_filename); } } else { - ($instr, $graph) = read_gcno($bb_filename, $base_dir); + ($instr, $graph) = read_gcno($bb_filename); } + # Try to find base directory automatically if requested by user + if ($rc_auto_base) { + $base_dir = find_base_from_graph($base_dir, $instr, $graph); + } + + ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph); + # Set $object_dir to real location of object files. This may differ # from $da_dir if the graph file is just a link to the "real" object # file location. @@ -850,6 +1014,7 @@ sub process_dafile($$) } # Change to directory containing data files and apply GCOV + debug("chdir($base_dir)\n"); chdir($base_dir); if ($da_renamed) @@ -905,7 +1070,7 @@ sub process_dafile($$) else { # Append to output file - open(INFO_HANDLE, ">>$output_filename") + open(INFO_HANDLE, ">>", $output_filename) or die("ERROR: cannot write to ". "$output_filename!\n"); } @@ -913,7 +1078,7 @@ sub process_dafile($$) else { # Open .info file for output - open(INFO_HANDLE, ">$da_filename.info") + open(INFO_HANDLE, ">", "$da_filename.info") or die("ERROR: cannot create $da_filename.info!\n"); } @@ -922,23 +1087,34 @@ sub process_dafile($$) # Traverse the list of generated .gcov files and combine them into a # single .info file - @unprocessed = keys(%{$instr}); foreach $gcov_file (sort(@gcov_list)) { my $i; my $num; + # Skip gcov file for gcc built-in code + next if ($gcov_file eq ".gcov"); + ($source, $object) = read_gcov_header($gcov_file); - if (defined($source)) - { - $source = solve_relative_path($base_dir, $source); + if (!defined($source)) { + # Derive source file name from gcov file name if + # header format could not be parsed + $source = $gcov_file; + $source =~ s/\.gcov$//; + } + + $source = solve_relative_path($base_dir, $source); + + if (defined($adjust_src_pattern)) { + # Apply transformation as specified by user + $source =~ s/$adjust_src_pattern/$adjust_src_replace/g; } # gcov will happily create output even if there's no source code # available - this interferes with checksum creation so we need # to pull the emergency brake here. - if (defined($source) && ! -r $source && $checksum) + if (! -r $source && $checksum) { if ($ignore[$ERROR_SOURCE]) { @@ -949,8 +1125,7 @@ sub process_dafile($$) die("ERROR: could not read source file $source\n"); } - @matches = match_filename(defined($source) ? $source : - $gcov_file, keys(%{$instr})); + @matches = match_filename($source, keys(%{$instr})); # Skip files that are not mentioned in the graph file if (!@matches) @@ -994,13 +1169,13 @@ sub process_dafile($$) \@matches, \@gcov_content); } - # Remove processed file from list - for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--) - { - if ($unprocessed[$index] eq $source_filename) - { - splice(@unprocessed, $index, 1); - last; + # Skip external files if requested + if (!$opt_external) { + if (is_external($source_filename)) { + info(" ignoring data for external file ". + "$source_filename\n"); + unlink($gcov_file); + next; } } @@ -1090,6 +1265,7 @@ sub process_dafile($$) my ($line, $block, $branch, $taken) = br_gvec_get($gcov_branches, $i); + $block = $BR_VEC_MAX if ($block < 0); print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); $br_found++; $br_hit++ if ($taken ne '-' && $taken > 0); @@ -1138,16 +1314,6 @@ sub process_dafile($$) unlink($gcov_file); } - # Check for files which show up in the graph file but were never - # processed - if (@unprocessed && @gcov_list) - { - foreach (@unprocessed) - { - warn("WARNING: no data found for $_\n"); - } - } - if (!($output_filename && ($output_filename eq "-"))) { close(INFO_HANDLE); @@ -1168,8 +1334,40 @@ sub solve_relative_path($$) { my $path = $_[0]; my $dir = $_[1]; + my $volume; + my $directories; + my $filename; + my @dirs; # holds path elements my $result; + # Convert from Windows path to msys path + if( $^O eq "msys" ) + { + # search for a windows drive letter at the beginning + ($volume, $directories, $filename) = File::Spec::Win32->splitpath( $dir ); + if( $volume ne '' ) + { + my $uppercase_volume; + # transform c/d\../e/f\g to Windows style c\d\..\e\f\g + $dir = File::Spec::Win32->canonpath( $dir ); + # use Win32 module to retrieve path components + # $uppercase_volume is not used any further + ( $uppercase_volume, $directories, $filename ) = File::Spec::Win32->splitpath( $dir ); + @dirs = File::Spec::Win32->splitdir( $directories ); + + # prepend volume, since in msys C: is always mounted to /c + $volume =~ s|^([a-zA-Z]+):|/\L$1\E|; + unshift( @dirs, $volume ); + + # transform to Unix style '/' path + $directories = File::Spec->catdir( @dirs ); + $dir = File::Spec->catpath( '', $directories, $filename ); + } else { + # eliminate '\' path separators + $dir = File::Spec->canonpath( $dir ); + } + } + $result = $dir; # Prepend path if not absolute if ($dir =~ /^[^\/]/) @@ -1182,6 +1380,10 @@ sub solve_relative_path($$) # Remove . $result =~ s/\/\.\//\//g; + $result =~ s/\/\.$/\//g; + + # Remove trailing / + $result =~ s/\/$//g; # Solve .. while ($result =~ s/\/[^\/]+\/\.\.\//\//) @@ -1266,7 +1468,7 @@ sub solve_ambiguous_match($$$) { # Compare file contents - open(SOURCE, $filename) + open(SOURCE, "<", $filename) or die("ERROR: cannot read $filename!\n"); $no_match = 0; @@ -1336,7 +1538,7 @@ sub read_gcov_header($) my $object; local *INPUT; - if (!open(INPUT, $_[0])) + if (!open(INPUT, "<", $_[0])) { if ($ignore_errors[$ERROR_GCOV]) { @@ -1408,6 +1610,7 @@ sub br_gvec_get($$) # Retrieve data from vector $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $block = -1 if ($block == $BR_VEC_MAX); $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); @@ -1435,6 +1638,7 @@ sub br_gvec_push($$$$$) $vec = "" if (!defined($vec)); $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; + $block = $BR_VEC_MAX if $block < 0; # Encode taken value into an integer if ($taken eq "-") { @@ -1484,11 +1688,13 @@ sub read_gcov_file($) my $number; my $exclude_flag = 0; my $exclude_line = 0; + my $exclude_br_flag = 0; + my $exclude_branch = 0; my $last_block = $UNNAMED_BLOCK; my $last_line = 0; local *INPUT; - if (!open(INPUT, $filename)) { + if (!open(INPUT, "<", $filename)) { if ($ignore_errors[$ERROR_GCOV]) { warn("WARNING: cannot read $filename!\n"); @@ -1508,11 +1714,15 @@ sub read_gcov_file($) s/\015$//; if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { + next if (!$br_coverage); next if ($exclude_line); + next if ($exclude_branch); $branches = br_gvec_push($branches, $last_line, $last_block, $1, $2); } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if (!$br_coverage); next if ($exclude_line); + next if ($exclude_branch); $branches = br_gvec_push($branches, $last_line, $last_block, $1, '-'); } @@ -1530,12 +1740,25 @@ sub read_gcov_file($) } elsif (/$EXCL_START/) { $exclude_flag = 1; } - if (/$EXCL_LINE/ || $exclude_flag) { + if (/$excl_line/ || $exclude_flag) { $exclude_line = 1; } else { $exclude_line = 0; } } + # Check for exclusion markers (branch exclude) + if (!$no_markers) { + if (/$EXCL_BR_STOP/) { + $exclude_br_flag = 0; + } elsif (/$EXCL_BR_START/) { + $exclude_br_flag = 1; + } + if (/$excl_br_line/ || $exclude_br_flag) { + $exclude_branch = 1; + } else { + $exclude_branch = 0; + } + } # Source code execution data if (/^\t\t(.*)$/) { @@ -1579,16 +1802,21 @@ sub read_gcov_file($) $last_line = $2; $last_block = $3; } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { + next if (!$br_coverage); next if ($exclude_line); + next if ($exclude_branch); $branches = br_gvec_push($branches, $last_line, $last_block, $1, $2); } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if (!$br_coverage); next if ($exclude_line); + next if ($exclude_branch); $branches = br_gvec_push($branches, $last_line, $last_block, $1, '-'); } - elsif (/^function\s+(\S+)\s+called\s+(\d+)/) + elsif (/^function\s+(.+)\s+called\s+(\d+)\s+/) { + next if (!$func_coverage); if ($exclude_line) { next; } @@ -1611,12 +1839,26 @@ sub read_gcov_file($) } elsif (/$EXCL_START/) { $exclude_flag = 1; } - if (/$EXCL_LINE/ || $exclude_flag) { + if (/$excl_line/ || $exclude_flag) { $exclude_line = 1; } else { $exclude_line = 0; } } + # Check for exclusion markers (branch exclude) + if (!$no_markers) { + if (/$EXCL_BR_STOP/) { + $exclude_br_flag = 0; + } elsif (/$EXCL_BR_START/) { + $exclude_br_flag = 1; + } + if (/$excl_br_line/ || $exclude_br_flag) { + $exclude_branch = 1; + } else { + $exclude_branch = 0; + } + } + # :: if ($line eq "0") { @@ -1636,7 +1878,7 @@ sub read_gcov_file($) push(@result, 0); } else { # Check for zero count - if ($count eq "#####") { + if ($count =~ /^[#=]/) { $count = 0; } push(@result, 1); @@ -1649,7 +1891,7 @@ sub read_gcov_file($) } close(INPUT); - if ($exclude_flag) { + if ($exclude_flag || $exclude_br_flag) { warn("WARNING: unterminated exclusion section in $filename\n"); } return(\@result, $branches, \@functions); @@ -1667,12 +1909,29 @@ sub get_gcov_version() local *HANDLE; my $version_string; my $result; + my $pipe_next_line; - open(GCOV_PIPE, "$gcov_tool -v |") + open(GCOV_PIPE, "-|", "$gcov_tool --version") or die("ERROR: cannot retrieve gcov version!\n"); $version_string = ; + # LLVM gcov keeps version information on the second line. + # For example, gcov --version yields: + # LLVM (http://llvm.org/): + # LLVM version 3.4svn + + $pipe_next_line = ; + # In case version information is on first line. + # For example, with Xcode 7.0 gcov --version yields: + # Apple LLVM 7.0.0 (clang-700.0.65) + + $version_string = $pipe_next_line if ($pipe_next_line && $version_string =~ /LLVM/); close(GCOV_PIPE); + # Remove version information in parenthesis to cope with the following: + # - gcov (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3) + # - gcov (crosstool-NG 1.18.0) 4.7.2 + $version_string =~ s/\([^\)]*\)//g; + $result = 0; if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) { @@ -1687,13 +1946,22 @@ sub get_gcov_version() $result = $1 << 16 | $2 << 8; } } - if ($version_string =~ /suse/i && $result == 0x30303 || - $version_string =~ /mandrake/i && $result == 0x30302) + if ($version_string =~ /LLVM/) { - info("Using compatibility mode for GCC 3.3 (hammer)\n"); - $compatibility = $COMPAT_HAMMER; + # Map LLVM versions to the version of GCC gcov which + # they emulate + if ($result >= 0x030400) + { + info("Found LLVM gcov version 3.4, which emulates gcov version 4.2\n"); + $result = 0x040200; + } + else + { + warn("This version of LLVM's gcov is unknown. Assuming it emulates GCC gcov version 4.2.\n"); + $result = 0x040200; + } } - return $result; + return ($result, $version_string); } @@ -1756,13 +2024,14 @@ sub system_no_output($@) local *OLD_STDOUT; # Save old stdout and stderr handles - ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); - ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); + ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); # Redirect to /dev/null - ($mode & 1) && open(STDOUT, ">/dev/null"); - ($mode & 2) && open(STDERR, ">/dev/null"); + ($mode & 1) && open(STDOUT, ">", "/dev/null"); + ($mode & 2) && open(STDERR, ">", "/dev/null"); + debug("system(".join(' ', @_).")\n"); system(@_); $result = $?; @@ -1771,8 +2040,8 @@ sub system_no_output($@) ($mode & 2) && close(STDERR); # Restore old handles - ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); - ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); + ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); return $result; } @@ -1793,7 +2062,7 @@ sub read_config($) my $value; local *HANDLE; - if (!open(HANDLE, "<$filename")) + if (!open(HANDLE, "<", $filename)) { warn("WARNING: cannot read configuration file $filename\n"); return undef; @@ -1832,8 +2101,8 @@ sub read_config($) # key_string => var_ref # # where KEY_STRING is a keyword and VAR_REF is a reference to an associated -# variable. If the global configuration hash CONFIG contains a value for -# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# variable. If the global configuration hashes CONFIG or OPT_RC contain a value +# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. # sub apply_config($) @@ -1842,8 +2111,9 @@ sub apply_config($) foreach (keys(%{$ref})) { - if (defined($config->{$_})) - { + if (defined($opt_rc{$_})) { + ${$ref->{$_}} = $opt_rc{$_}; + } elsif (defined($config->{$_})) { ${$ref->{$_}} = $config->{$_}; } } @@ -1865,7 +2135,7 @@ sub get_exclusion_data($) my $flag = 0; local *HANDLE; - if (!open(HANDLE, "<$filename")) { + if (!open(HANDLE, "<", $filename)) { warn("WARNING: could not open $filename\n"); return undef; } @@ -1875,7 +2145,7 @@ sub get_exclusion_data($) } elsif (/$EXCL_START/) { $flag = 1; } - if (/$EXCL_LINE/ || $flag) { + if (/$excl_line/ || $flag) { $list{$.} = 1; } } @@ -1989,12 +2259,7 @@ sub apply_exclusion_data($$) } # Store modified list - if (scalar(@new_data) > 0) { - $instr->{$filename} = \@new_data; - } else { - # All of this file was excluded - delete($instr->{$filename}); - } + $instr->{$filename} = \@new_data; } return ($instr, $graph); @@ -2023,11 +2288,10 @@ sub process_graphfile($$) # Get directory and basename of data file ($graph_dir, $graph_basename) = split_filename($graph_filename); - # avoid files from .libs dirs - if ($compat_libtool && $graph_dir =~ m/(.*)\/\.libs$/) { - $source_dir = $1; - } else { - $source_dir = $graph_dir; + $source_dir = $graph_dir; + if (is_compat($COMPAT_MODE_LIBTOOL)) { + # Avoid files from .libs dirs + $source_dir =~ s/\.libs$//; } # Construct base_dir for current file @@ -2040,22 +2304,36 @@ sub process_graphfile($$) $base_dir = $source_dir; } + # Ignore empty graph file (e.g. source file with no statement) + if (-z $graph_filename) + { + warn("WARNING: empty $graph_filename (skipped)\n"); + return; + } + if ($gcov_version < $GCOV_VERSION_3_4_0) { - if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + if (is_compat($COMPAT_MODE_HAMMER)) { - ($instr, $graph) = read_bbg($graph_filename, $base_dir); + ($instr, $graph) = read_bbg($graph_filename); } else { - ($instr, $graph) = read_bb($graph_filename, $base_dir); + ($instr, $graph) = read_bb($graph_filename); } } else { - ($instr, $graph) = read_gcno($graph_filename, $base_dir); + ($instr, $graph) = read_gcno($graph_filename); } + # Try to find base directory automatically if requested by user + if ($rc_auto_base) { + $base_dir = find_base_from_graph($base_dir, $instr, $graph); + } + + ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph); + if (!$no_markers) { # Apply exclusion marker data to graph file data ($instr, $graph) = apply_exclusion_data($instr, $graph); @@ -2071,7 +2349,7 @@ sub process_graphfile($$) else { # Append to output file - open(INFO_HANDLE, ">>$output_filename") + open(INFO_HANDLE, ">>", $output_filename) or die("ERROR: cannot write to ". "$output_filename!\n"); } @@ -2079,7 +2357,7 @@ sub process_graphfile($$) else { # Open .info file for output - open(INFO_HANDLE, ">$graph_filename.info") + open(INFO_HANDLE, ">", "$graph_filename.info") or die("ERROR: cannot create $graph_filename.info!\n"); } @@ -2091,9 +2369,18 @@ sub process_graphfile($$) my $line; my $linedata; + # Skip external files if requested + if (!$opt_external) { + if (is_external($filename)) { + info(" ignoring data for external file ". + "$filename\n"); + next; + } + } + print(INFO_HANDLE "SF:$filename\n"); - if (defined($funcdata)) { + if (defined($funcdata) && $func_coverage) { my @functions = sort {$funcdata->{$a}->[0] <=> $funcdata->{$b}->[0]} keys(%{$funcdata}); @@ -2194,26 +2481,36 @@ sub graph_expect($) } # -# graph_read(handle, bytes[, description]) +# graph_read(handle, bytes[, description, peek]) # # Read and return the specified number of bytes from handle. Return undef -# if the number of bytes could not be read. +# if the number of bytes could not be read. If PEEK is non-zero, reset +# file position after read. # -sub graph_read(*$;$) +sub graph_read(*$;$$) { - my ($handle, $length, $desc) = @_; + my ($handle, $length, $desc, $peek) = @_; my $data; my $result; + my $pos; graph_expect($desc); + if ($peek) { + $pos = tell($handle); + if ($pos == -1) { + warn("Could not get current file position: $!\n"); + return undef; + } + } $result = read($handle, $data, $length); if ($debug) { + my $op = $peek ? "peek" : "read"; my $ascii = ""; my $hex = ""; my $i; - print(STDERR "DEBUG: read($length)=$result: "); + print(STDERR "DEBUG: $op($length)=$result: "); for ($i = 0; $i < length($data); $i++) { my $c = substr($data, $i, 1);; my $n = ord($c); @@ -2228,6 +2525,12 @@ sub graph_read(*$;$) print(STDERR "$hex |$ascii|"); print(STDERR "\n"); } + if ($peek) { + if (!seek($handle, $pos, 0)) { + warn("Could not set file position: $!\n"); + return undef; + } + } if ($result != $length) { return undef; } @@ -2252,6 +2555,27 @@ sub graph_skip(*$;$) } # +# uniq(list) +# +# Return list without duplicate entries. +# + +sub uniq(@) +{ + my (@list) = @_; + my @new_list; + my %known; + + foreach my $item (@list) { + next if ($known{$item}); + $known{$item} = 1; + push(@new_list, $item); + } + + return @new_list; +} + +# # sort_uniq(list) # # Return list in numerically ascending order and without duplicate entries. @@ -2286,6 +2610,133 @@ sub sort_uniq_lex(@) } # +# parent_dir(dir) +# +# Return parent directory for DIR. DIR must not contain relative path +# components. +# + +sub parent_dir($) +{ + my ($dir) = @_; + my ($v, $d, $f) = splitpath($dir, 1); + my @dirs = splitdir($d); + + pop(@dirs); + + return catpath($v, catdir(@dirs), $f); +} + +# +# find_base_from_graph(base_dir, instr, graph) +# +# Try to determine the base directory of the graph file specified by INSTR +# and GRAPH. The base directory is the base for all relative filenames in +# the graph file. It is defined by the current working directory at time +# of compiling the source file. +# +# This function implements a heuristic which relies on the following +# assumptions: +# - all files used for compilation are still present at their location +# - the base directory is either BASE_DIR or one of its parent directories +# - files by the same name are not present in multiple parent directories +# + +sub find_base_from_graph($$$) +{ + my ($base_dir, $instr, $graph) = @_; + my $old_base; + my $best_miss; + my $best_base; + my %rel_files; + + # Determine list of relative paths + foreach my $filename (keys(%{$instr}), keys(%{$graph})) { + next if (file_name_is_absolute($filename)); + + $rel_files{$filename} = 1; + } + + # Early exit if there are no relative paths + return $base_dir if (!%rel_files); + + do { + my $miss = 0; + + foreach my $filename (keys(%rel_files)) { + if (!-e solve_relative_path($base_dir, $filename)) { + $miss++; + } + } + + debug("base_dir=$base_dir miss=$miss\n"); + + # Exit if we find an exact match with no misses + return $base_dir if ($miss == 0); + + # No exact match, aim for the one with the least source file + # misses + if (!defined($best_base) || $miss < $best_miss) { + $best_base = $base_dir; + $best_miss = $miss; + } + + # Repeat until there's no more parent directory + $old_base = $base_dir; + $base_dir = parent_dir($base_dir); + } while ($old_base ne $base_dir); + + return $best_base; +} + +# +# adjust_graph_filenames(base_dir, instr, graph) +# +# Make relative paths in INSTR and GRAPH absolute and apply +# geninfo_adjust_src_path setting to graph file data. +# + +sub adjust_graph_filenames($$$) +{ + my ($base_dir, $instr, $graph) = @_; + + foreach my $filename (keys(%{$instr})) { + my $old_filename = $filename; + + # Convert to absolute canonical form + $filename = solve_relative_path($base_dir, $filename); + + # Apply adjustment + if (defined($adjust_src_pattern)) { + $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g; + } + + if ($filename ne $old_filename) { + $instr->{$filename} = delete($instr->{$old_filename}); + } + } + + foreach my $filename (keys(%{$graph})) { + my $old_filename = $filename; + + # Make absolute + # Convert to absolute canonical form + $filename = solve_relative_path($base_dir, $filename); + + # Apply adjustment + if (defined($adjust_src_pattern)) { + $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g; + } + + if ($filename ne $old_filename) { + $graph->{$filename} = delete($graph->{$old_filename}); + } + } + + return ($instr, $graph); +} + +# # graph_cleanup(graph) # # Remove entries for functions with no lines. Remove duplicate line numbers. @@ -2310,7 +2761,7 @@ sub graph_cleanup($) next; } # Normalize list - $per_file->{$function} = [ sort_uniq(@$lines) ]; + $per_file->{$function} = [ uniq(@$lines) ]; } if (scalar(keys(%{$per_file})) == 0) { # Remove empty file @@ -2454,6 +2905,7 @@ sub graph_add_order($$$) push(@$list, $filename); $fileorder->{$function} = $list; } + # # read_bb_word(handle[, description]) # @@ -2513,7 +2965,7 @@ sub read_bb_string(*$) } # -# read_bb(filename, base_dir) +# read_bb(filename) # # Read the contents of the specified .bb file and return (instr, graph), where: # @@ -2524,14 +2976,12 @@ sub read_bb_string(*$) # file_data : function name -> line_data # line_data : [ line1, line2, ... ] # -# Relative filenames are converted to absolute form using base_dir as -# base directory. See the gcov info pages of gcc 2.95 for a description of -# the .bb file format. +# See the gcov info pages of gcc 2.95 for a description of the .bb file format. # -sub read_bb($$) +sub read_bb($) { - my ($bb_filename, $base) = @_; + my ($bb_filename) = @_; my $minus_one = 0x80000001; my $minus_two = 0x80000002; my $value; @@ -2543,7 +2993,7 @@ sub read_bb($$) my $graph; local *HANDLE; - open(HANDLE, "<$bb_filename") or goto open_error; + open(HANDLE, "<", $bb_filename) or goto open_error; binmode(HANDLE); while (!eof(HANDLE)) { $value = read_bb_value(*HANDLE, "data word"); @@ -2553,10 +3003,6 @@ sub read_bb($$) graph_expect("filename"); $filename = read_bb_string(*HANDLE, $minus_one); goto incomplete if (!defined($filename)); - if ($filename ne "") { - $filename = solve_relative_path($base, - $filename); - } } elsif ($value == $minus_two) { # Function name graph_expect("function name"); @@ -2647,16 +3093,15 @@ sub read_bbg_string(*) # # read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, -# function, base) +# function) # # Read a bbg format lines record from handle and add the relevant data to # bb and fileorder. Return filename on success, undef on error. # -sub read_bbg_lines_record(*$$$$$$) +sub read_bbg_lines_record(*$$$$$) { - my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function, - $base) = @_; + my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_; my $string; my $lineno; @@ -2676,7 +3121,10 @@ sub read_bbg_lines_record(*$$$$$$) if ($string eq "") { return $filename; } - $filename = solve_relative_path($base, $string); + $filename = $string; + if (!exists($bb->{$function}->{$filename})) { + $bb->{$function}->{$filename} = []; + } next; } # Got an actual line number @@ -2691,21 +3139,20 @@ sub read_bbg_lines_record(*$$$$$$) } # -# read_bbg(filename, base_dir) +# read_bbg(filename) # # Read the contents of the specified .bbg file and return the following mapping: # graph: filename -> file_data # file_data: function name -> line_data # line_data: [ line1, line2, ... ] # -# Relative filenames are converted to absolute form using base_dir as -# base directory. See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code -# for a description of the .bbg format. +# See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code for a description +# of the .bbg format. # -sub read_bbg($$) +sub read_bbg($) { - my ($bbg_filename, $base) = @_; + my ($bbg_filename) = @_; my $file_magic = 0x67626267; my $tag_function = 0x01000000; my $tag_lines = 0x01450000; @@ -2720,7 +3167,7 @@ sub read_bbg($$) my $graph; local *HANDLE; - open(HANDLE, "<$bbg_filename") or goto open_error; + open(HANDLE, "<", $bbg_filename) or goto open_error; binmode(HANDLE); # Read magic $word = read_bbg_value(*HANDLE, "file magic"); @@ -2752,7 +3199,7 @@ sub read_bbg($$) # Read lines record $filename = read_bbg_lines_record(HANDLE, $bbg_filename, $bb, $fileorder, $filename, - $function, $base); + $function); goto incomplete if (!defined($filename)); } else { # Skip record contents @@ -2778,31 +3225,33 @@ magic_error: } # -# read_gcno_word(handle[, description]) +# read_gcno_word(handle[, description, peek]) # # Read and return a word in .gcno format. # -sub read_gcno_word(*;$) +sub read_gcno_word(*;$$) { - my ($handle, $desc) = @_; + my ($handle, $desc, $peek) = @_; - return graph_read($handle, 4, $desc); + return graph_read($handle, 4, $desc, $peek); } # -# read_gcno_value(handle, big_endian[, description]) +# read_gcno_value(handle, big_endian[, description, peek]) # # Read a word in .gcno format from handle and return its integer value -# according to the specified endianness. +# according to the specified endianness. If PEEK is non-zero, reset file +# position after read. # -sub read_gcno_value(*$;$) +sub read_gcno_value(*$;$$) { - my ($handle, $big_endian, $desc) = @_; + my ($handle, $big_endian, $desc, $peek) = @_; my $word; + my $pos; - $word = read_gcno_word($handle, $desc); + $word = read_gcno_word($handle, $desc, $peek); return undef if (!defined($word)); if ($big_endian) { return unpack("N", $word); @@ -2841,16 +3290,16 @@ sub read_gcno_string(*$) # # read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, -# function, base, big_endian) +# function, big_endian) # # Read a gcno format lines record from handle and add the relevant data to # bb and fileorder. Return filename on success, undef on error. # -sub read_gcno_lines_record(*$$$$$$$) +sub read_gcno_lines_record(*$$$$$$) { my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, - $base, $big_endian) = @_; + $big_endian) = @_; my $string; my $lineno; @@ -2870,7 +3319,10 @@ sub read_gcno_lines_record(*$$$$$$$) if ($string eq "") { return $filename; } - $filename = solve_relative_path($base, $string); + $filename = $string; + if (!exists($bb->{$function}->{$filename})) { + $bb->{$function}->{$filename} = []; + } next; } # Got an actual line number @@ -2886,7 +3338,52 @@ sub read_gcno_lines_record(*$$$$$$$) } # -# read_gcno_function_record(handle, graph, base, big_endian) +# determine_gcno_split_crc(handle, big_endian, rec_length) +# +# Determine if HANDLE refers to a .gcno file with a split checksum function +# record format. Return non-zero in case of split checksum format, zero +# otherwise, undef in case of read error. +# + +sub determine_gcno_split_crc($$$) +{ + my ($handle, $big_endian, $rec_length) = @_; + my $strlen; + my $overlong_string; + + return 1 if ($gcov_version >= $GCOV_VERSION_4_7_0); + return 1 if (is_compat($COMPAT_MODE_SPLIT_CRC)); + + # Heuristic: + # Decide format based on contents of next word in record: + # - pre-gcc 4.7 + # This is the function name length / 4 which should be + # less than the remaining record length + # - gcc 4.7 + # This is a checksum, likely with high-order bits set, + # resulting in a large number + $strlen = read_gcno_value($handle, $big_endian, undef, 1); + return undef if (!defined($strlen)); + $overlong_string = 1 if ($strlen * 4 >= $rec_length - 12); + + if ($overlong_string) { + if (is_compat_auto($COMPAT_MODE_SPLIT_CRC)) { + info("Auto-detected compatibility mode for split ". + "checksum .gcno file format\n"); + + return 1; + } else { + # Sanity check + warn("Found overlong string in function record: ". + "try '--compat split_crc'\n"); + } + } + + return 0; +} + +# +# read_gcno_function_record(handle, graph, big_endian, rec_length) # # Read a gcno format function record from handle and add the relevant data # to graph. Return (filename, function) on success, undef on error. @@ -2894,7 +3391,7 @@ sub read_gcno_lines_record(*$$$$$$$) sub read_gcno_function_record(*$$$$) { - my ($handle, $bb, $fileorder, $base, $big_endian) = @_; + my ($handle, $bb, $fileorder, $big_endian, $rec_length) = @_; my $filename; my $function; my $lineno; @@ -2903,6 +3400,14 @@ sub read_gcno_function_record(*$$$$) graph_expect("function record"); # Skip ident and checksum graph_skip($handle, 8, "function ident and checksum") or return undef; + # Determine if this is a function record with split checksums + if (!defined($gcno_split_crc)) { + $gcno_split_crc = determine_gcno_split_crc($handle, $big_endian, + $rec_length); + return undef if (!defined($gcno_split_crc)); + } + # Skip cfg checksum word in case of split checksums + graph_skip($handle, 4, "function cfg checksum") if ($gcno_split_crc); # Read function name graph_expect("function name"); $function = read_gcno_string($handle, $big_endian); @@ -2911,7 +3416,6 @@ sub read_gcno_function_record(*$$$$) graph_expect("filename"); $filename = read_gcno_string($handle, $big_endian); return undef if (!defined($filename)); - $filename = solve_relative_path($base, $filename); # Read first line number $lineno = read_gcno_value($handle, $big_endian, "initial line number"); return undef if (!defined($lineno)); @@ -2923,7 +3427,7 @@ sub read_gcno_function_record(*$$$$) } # -# read_gcno(filename, base_dir) +# read_gcno(filename) # # Read the contents of the specified .gcno file and return the following # mapping: @@ -2931,14 +3435,13 @@ sub read_gcno_function_record(*$$$$) # file_data: function name -> line_data # line_data: [ line1, line2, ... ] # -# Relative filenames are converted to absolute form using base_dir as -# base directory. See the gcov-io.h file in the gcc 3.3 source code -# for a description of the .gcno format. +# See the gcov-io.h file in the gcc 3.3 source code for a description of +# the .gcno format. # -sub read_gcno($$) +sub read_gcno($) { - my ($gcno_filename, $base) = @_; + my ($gcno_filename) = @_; my $file_magic = 0x67636e6f; my $tag_function = 0x01000000; my $tag_lines = 0x01450000; @@ -2952,9 +3455,11 @@ sub read_gcno($$) my $fileorder = {}; my $instr; my $graph; + my $filelength; local *HANDLE; - open(HANDLE, "<$gcno_filename") or goto open_error; + open(HANDLE, "<", $gcno_filename) or goto open_error; + $filelength = (stat(HANDLE))[7]; binmode(HANDLE); # Read magic $word = read_gcno_word(*HANDLE, "file magic"); @@ -2986,16 +3491,25 @@ sub read_gcno($$) $next_pos = tell(HANDLE); goto tell_error if ($next_pos == -1); $next_pos += $length; + # Catch garbage at the end of a gcno file + if ($next_pos > $filelength) { + debug("Overlong record: file_length=$filelength ". + "rec_length=$length\n"); + warn("WARNING: $gcno_filename: Overlong record at end ". + "of file!\n"); + last; + } # Process record if ($tag == $tag_function) { ($filename, $function) = read_gcno_function_record( - *HANDLE, $bb, $fileorder, $base, $big_endian); + *HANDLE, $bb, $fileorder, $big_endian, + $length); goto incomplete if (!defined($function)); } elsif ($tag == $tag_lines) { # Read lines record $filename = read_gcno_lines_record(*HANDLE, $gcno_filename, $bb, $fileorder, - $filename, $function, $base, + $filename, $function, $big_endian); goto incomplete if (!defined($filename)); } else { @@ -3053,16 +3567,236 @@ sub get_gcov_capabilities() { my $help = `$gcov_tool --help`; my %capabilities; + my %short_option_translations = ( + 'a' => 'all-blocks', + 'b' => 'branch-probabilities', + 'c' => 'branch-counts', + 'f' => 'function-summaries', + 'h' => 'help', + 'l' => 'long-file-names', + 'n' => 'no-output', + 'o' => 'object-directory', + 'p' => 'preserve-paths', + 'u' => 'unconditional-branches', + 'v' => 'version', + ); foreach (split(/\n/, $help)) { - next if (!/--(\S+)/); - next if ($1 eq 'help'); - next if ($1 eq 'version'); - next if ($1 eq 'object-directory'); + my $capability; + if (/--(\S+)/) { + $capability = $1; + } else { + # If the line provides a short option, translate it. + next if (!/^\s*-(\S)\s/); + $capability = $short_option_translations{$1}; + next if not defined($capability); + } + next if ($capability eq 'help'); + next if ($capability eq 'version'); + next if ($capability eq 'object-directory'); - $capabilities{$1} = 1; - debug("gcov has capability '$1'\n"); + $capabilities{$capability} = 1; + debug("gcov has capability '$capability'\n"); } return \%capabilities; } + +# +# parse_ignore_errors(@ignore_errors) +# +# Parse user input about which errors to ignore. +# + +sub parse_ignore_errors(@) +{ + my (@ignore_errors) = @_; + my @items; + my $item; + + return if (!@ignore_errors); + + foreach $item (@ignore_errors) { + $item =~ s/\s//g; + if ($item =~ /,/) { + # Split and add comma-separated parameters + push(@items, split(/,/, $item)); + } else { + # Add single parameter + push(@items, $item); + } + } + foreach $item (@items) { + my $item_id = $ERROR_ID{lc($item)}; + + if (!defined($item_id)) { + die("ERROR: unknown argument for --ignore-errors: ". + "$item\n"); + } + $ignore[$item_id] = 1; + } +} + +# +# is_external(filename) +# +# Determine if a file is located outside of the specified data directories. +# + +sub is_external($) +{ + my ($filename) = @_; + my $dir; + + foreach $dir (@internal_dirs) { + return 0 if ($filename =~ /^\Q$dir\/\E/); + } + return 1; +} + +# +# compat_name(mode) +# +# Return the name of compatibility mode MODE. +# + +sub compat_name($) +{ + my ($mode) = @_; + my $name = $COMPAT_MODE_TO_NAME{$mode}; + + return $name if (defined($name)); + + return ""; +} + +# +# parse_compat_modes(opt) +# +# Determine compatibility mode settings. +# + +sub parse_compat_modes($) +{ + my ($opt) = @_; + my @opt_list; + my %specified; + + # Initialize with defaults + %compat_value = %COMPAT_MODE_DEFAULTS; + + # Add old style specifications + if (defined($opt_compat_libtool)) { + $compat_value{$COMPAT_MODE_LIBTOOL} = + $opt_compat_libtool ? $COMPAT_VALUE_ON + : $COMPAT_VALUE_OFF; + } + + # Parse settings + if (defined($opt)) { + @opt_list = split(/\s*,\s*/, $opt); + } + foreach my $directive (@opt_list) { + my ($mode, $value); + + # Either + # mode=off|on|auto or + # mode (implies on) + if ($directive !~ /^(\w+)=(\w+)$/ && + $directive !~ /^(\w+)$/) { + die("ERROR: Unknown compatibility mode specification: ". + "$directive!\n"); + } + # Determine mode + $mode = $COMPAT_NAME_TO_MODE{lc($1)}; + if (!defined($mode)) { + die("ERROR: Unknown compatibility mode '$1'!\n"); + } + $specified{$mode} = 1; + # Determine value + if (defined($2)) { + $value = $COMPAT_NAME_TO_VALUE{lc($2)}; + if (!defined($value)) { + die("ERROR: Unknown compatibility mode ". + "value '$2'!\n"); + } + } else { + $value = $COMPAT_VALUE_ON; + } + $compat_value{$mode} = $value; + } + # Perform auto-detection + foreach my $mode (sort(keys(%compat_value))) { + my $value = $compat_value{$mode}; + my $is_autodetect = ""; + my $name = compat_name($mode); + + if ($value == $COMPAT_VALUE_AUTO) { + my $autodetect = $COMPAT_MODE_AUTO{$mode}; + + if (!defined($autodetect)) { + die("ERROR: No auto-detection for ". + "mode '$name' available!\n"); + } + + if (ref($autodetect) eq "CODE") { + $value = &$autodetect(); + $compat_value{$mode} = $value; + $is_autodetect = " (auto-detected)"; + } + } + + if ($specified{$mode}) { + if ($value == $COMPAT_VALUE_ON) { + info("Enabling compatibility mode ". + "'$name'$is_autodetect\n"); + } elsif ($value == $COMPAT_VALUE_OFF) { + info("Disabling compatibility mode ". + "'$name'$is_autodetect\n"); + } else { + info("Using delayed auto-detection for ". + "compatibility mode ". + "'$name'\n"); + } + } + } +} + +sub compat_hammer_autodetect() +{ + if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 || + $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302) + { + info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n"); + return $COMPAT_VALUE_ON; + } + return $COMPAT_VALUE_OFF; +} + +# +# is_compat(mode) +# +# Return non-zero if compatibility mode MODE is enabled. +# + +sub is_compat($) +{ + my ($mode) = @_; + + return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON); + return 0; +} + +# +# is_compat_auto(mode) +# +# Return non-zero if compatibility mode MODE is set to auto-detect. +# + +sub is_compat_auto($) +{ + my ($mode) = @_; + + return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO); + return 0; +} diff --git a/3rdParty/LCov/genpng b/3rdParty/LCov/genpng index 7fe9dfe..55e013e 100755 --- a/3rdParty/LCov/genpng +++ b/3rdParty/LCov/genpng @@ -22,7 +22,7 @@ # This script creates an overview PNG image of a source code file by # representing each source code character by a single pixel. # -# Note that the PERL module GD.pm is required for this script to work. +# Note that the Perl module GD.pm is required for this script to work. # It may be obtained from http://www.cpan.org # # History: @@ -32,10 +32,12 @@ use strict; use File::Basename; use Getopt::Long; +use Cwd qw/abs_path/; # Constants -our $lcov_version = 'LCOV version 1.9'; +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.12"; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); @@ -53,9 +55,6 @@ sub genpng_die_handler($); # Code entry point # -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; - # Check whether required module GD.pm is installed if (check_and_load_module("GD")) { @@ -182,7 +181,7 @@ sub genpng_process_file($$$$) local *HANDLE; my @source; - open(HANDLE, "<$filename") + open(HANDLE, "<", $filename) or die("ERROR: cannot open $filename!\n"); # Check for .gcov filename extension @@ -238,7 +237,7 @@ sub gen_png($$$@) my $overview_width = shift(@_); # Imagewidth for image my $tab_size = shift(@_); # Replacement string for tab signs my @source = @_; # Source code as passed via argument 2 - my $height = scalar(@source); # Height as define by source size + my $height; # Height as define by source size my $overview; # Source code overview image data my $col_plain_back; # Color for overview background my $col_plain_text; # Color for uninstrumented text @@ -261,6 +260,11 @@ sub gen_png($$$@) my $replacement; # Replacement string for tabulator chars local *PNG_HANDLE; # Handle for output PNG file + # Handle empty source files + if (!@source) { + @source = ( "" ); + } + $height = scalar(@source); # Create image $overview = new GD::Image($overview_width, $height) or die("ERROR: cannot allocate overview image!\n"); @@ -362,7 +366,7 @@ sub gen_png($$$@) } # Write PNG file - open (PNG_HANDLE, ">$filename") + open (PNG_HANDLE, ">", $filename) or die("ERROR: cannot write png file $filename!\n"); binmode(*PNG_HANDLE); print(PNG_HANDLE $overview->png()); diff --git a/3rdParty/LCov/lcov b/3rdParty/LCov/lcov index 4e392ff..7760ba2 100755 --- a/3rdParty/LCov/lcov +++ b/3rdParty/LCov/lcov @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) International Business Machines Corp., 2002,2010 +# Copyright (c) International Business Machines Corp., 2002,2012 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -71,7 +71,8 @@ use Cwd qw /abs_path getcwd/; # Global constants -our $lcov_version = 'LCOV version 1.9'; +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.12"; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); @@ -93,6 +94,7 @@ our $BR_BRANCH = 1; our $BR_TAKEN = 2; our $BR_VEC_ENTRIES = 3; our $BR_VEC_WIDTH = 32; +our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); # Branch data combination types our $BR_SUB = 0; @@ -139,6 +141,8 @@ sub lcov_geninfo(@); sub create_package($$$;$); sub get_func_found_and_hit($); sub br_ivec_get($$); +sub summary(); +sub rate($$;$$$); # Global variables & initialization our @directory; # Specifies where to get coverage data from @@ -168,7 +172,7 @@ our $no_checksum; # If set, don't calculate a checksum for each line our $compat_libtool; # If set, indicates that libtool mode is to be enabled our $no_compat_libtool; # If set, indicates that libtool mode is to be disabled our $gcov_tool; -our $ignore_errors; +our @opt_ignore_errors; our $initial; our $no_recursion = 0; our $to_package; @@ -177,7 +181,6 @@ our $maxdepth; our $no_markers; our $config; # Configuration file contents chomp($cwd); -our $tool_dir = dirname($0); # Directory where genhtml tool is installed our @temp_dirs; our $gcov_gkv; # gcov kernel support version found on machine our $opt_derive_func_data; @@ -186,12 +189,20 @@ our $opt_list_full_path; our $opt_no_list_full_path; our $opt_list_width = 80; our $opt_list_truncate_max = 20; +our $opt_external; +our $opt_no_external; +our $opt_config_file; +our %opt_rc; +our @opt_summary; +our $opt_compat; our $ln_overall_found; our $ln_overall_hit; our $fn_overall_found; our $fn_overall_hit; our $br_overall_found; our $br_overall_hit; +our $func_coverage = 1; +our $br_coverage = 0; # @@ -203,17 +214,29 @@ $SIG{__DIE__} = \&die_handler; $SIG{'INT'} = \&abort_handler; $SIG{'QUIT'} = \&abort_handler; -# Prettify version string -$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; +# Check command line for a configuration file name +Getopt::Long::Configure("pass_through", "no_auto_abbrev"); +GetOptions("config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc); +Getopt::Long::Configure("default"); -# Add current working directory if $tool_dir is not already an absolute path -if (! ($tool_dir =~ /^\/(.*)$/)) { - $tool_dir = "$cwd/$tool_dir"; + # Remove spaces around rc options + my %new_opt_rc; + + while (my ($key, $value) = each(%opt_rc)) { + $key =~ s/^\s+|\s+$//g; + $value =~ s/^\s+|\s+$//g; + + $new_opt_rc{$key} = $value; + } + %opt_rc = %new_opt_rc; } # Read configuration file if available -if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +if (defined($opt_config_file)) { + $config = read_config($opt_config_file); +} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) { $config = read_config($ENV{"HOME"}."/.lcovrc"); } @@ -222,15 +245,17 @@ elsif (-r "/etc/lcovrc") $config = read_config("/etc/lcovrc"); } -if ($config) +if ($config || %opt_rc) { - # Copy configuration file values to variables + # Copy configuration file and --rc values to variables apply_config({ "lcov_gcov_dir" => \$gcov_dir, "lcov_tmp_dir" => \$tmp_dir, "lcov_list_full_path" => \$opt_list_full_path, "lcov_list_width" => \$opt_list_width, "lcov_list_truncate_max"=> \$opt_list_truncate_max, + "lcov_branch_coverage" => \$br_coverage, + "lcov_function_coverage"=> \$func_coverage, }); } @@ -259,7 +284,7 @@ if (!GetOptions("directory|d|di=s" => \@directory, "compat-libtool" => \$compat_libtool, "no-compat-libtool" => \$no_compat_libtool, "gcov-tool=s" => \$gcov_tool, - "ignore-errors=s" => \$ignore_errors, + "ignore-errors=s" => \@opt_ignore_errors, "initial|i" => \$initial, "no-recursion" => \$no_recursion, "to-package=s" => \$to_package, @@ -269,6 +294,12 @@ if (!GetOptions("directory|d|di=s" => \@directory, "debug" => \$opt_debug, "list-full-path" => \$opt_list_full_path, "no-list-full-path" => \$opt_no_list_full_path, + "external" => \$opt_external, + "no-external" => \$opt_no_external, + "summary=s" => \@opt_summary, + "compat=s" => \$opt_compat, + "config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc, )) { print(STDERR "Use $tool_name --help to get usage information\n"); @@ -294,6 +325,11 @@ else $opt_list_full_path = ($opt_no_list_full_path ? 0 : 1); $opt_no_list_full_path = undef; } + + if (defined($opt_no_external)) { + $opt_external = 0; + $opt_no_external = undef; + } } # Check for help option @@ -341,7 +377,7 @@ else check_options(); # Only --extract, --remove and --diff allow unnamed parameters -if (@ARGV && !($extract || $remove || $diff)) +if (@ARGV && !($extract || $remove || $diff || @opt_summary)) { die("Extra parameter found: '".join(" ", @ARGV)."'\n". "Use $tool_name --help to get usage information\n"); @@ -429,6 +465,12 @@ elsif ($diff) $fn_overall_found, $fn_overall_hit, $br_overall_found, $br_overall_hit) = diff(); } +elsif (@opt_summary) +{ + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = summary(); +} temp_cleanup(); @@ -471,6 +513,7 @@ Operation: -r, --remove FILE PATTERN Remove files matching PATTERN from FILE -l, --list FILE List contents of tracefile FILE --diff FILE DIFF Transform tracefile FILE according to DIFF + --summary FILE Show summary coverage data for tracefiles Options: -i, --initial Capture initial zero coverage data @@ -493,6 +536,10 @@ Options: --no-markers Ignore exclusion markers in source code --derive-func-data Generate function data from line data --list-full-path Print full path during a list operation + --(no-)external Include (ignore) data for external files + --config-file FILENAME Specify configuration file location + --rc SETTING=VALUE Override configuration file setting + --compat MODE=on|off|auto Set compat MODE (libtool, hammer, split_crc) For more information see: $lcov_url END_OF_USAGE @@ -518,17 +565,18 @@ sub check_options() $remove && $i++; $list && $i++; $diff && $i++; + @opt_summary && $i++; if ($i == 0) { - die("Need one of the options -z, -c, -a, -e, -r, -l or ". - "--diff\n". + die("Need one of options -z, -c, -a, -e, -r, -l, ". + "--diff or --summary\n". "Use $tool_name --help to get usage information\n"); } elsif ($i > 1) { - die("ERROR: only one of -z, -c, -a, -e, -r, -l or ". - "--diff allowed!\n". + die("ERROR: only one of -z, -c, -a, -e, -r, -l, ". + "--diff or --summary allowed!\n". "Use $tool_name --help to get usage information\n"); } } @@ -551,7 +599,7 @@ sub userspace_reset() { info("Deleting all .da files in $current_dir". ($no_recursion?"\n":" and subdirectories\n")); - @file_list = `find "$current_dir" $maxdepth $follow -name \\*\\.da -o -name \\*\\.gcda -type f 2>/dev/null`; + @file_list = `find "$current_dir" $maxdepth $follow -name \\*\\.da -type f -o -name \\*\\.gcda -type f 2>/dev/null`; chomp(@file_list); foreach (@file_list) { @@ -613,7 +661,7 @@ sub kernel_reset() } else { die("ERROR: no reset control found in $gcov_dir\n"); } - open(HANDLE, ">$reset_file") or + open(HANDLE, ">", $reset_file) or die("ERROR: cannot write to $reset_file!\n"); print(HANDLE "0"); close(HANDLE); @@ -635,10 +683,10 @@ sub lcov_copy_single($$) local $/; local *HANDLE; - open(HANDLE, "<$from") or die("ERROR: cannot read $from: $!\n"); + open(HANDLE, "<", $from) or die("ERROR: cannot read $from: $!\n"); $content = ; close(HANDLE); - open(HANDLE, ">$to") or die("ERROR: cannot write $from: $!\n"); + open(HANDLE, ">", $to) or die("ERROR: cannot write $from: $!\n"); if (defined($content)) { print(HANDLE $content); } @@ -798,9 +846,11 @@ sub lcov_geninfo(@) { @param = (@param, "--gcov-tool", $gcov_tool); } - if ($ignore_errors) - { - @param = (@param, "--ignore-errors", $ignore_errors); + foreach (@opt_ignore_errors) { + @param = (@param, "--ignore-errors", $_); + } + if ($no_recursion) { + @param = (@param, "--no-recursion"); } if ($initial) { @@ -818,6 +868,26 @@ sub lcov_geninfo(@) { @param = (@param, "--debug"); } + if (defined($opt_external) && $opt_external) + { + @param = (@param, "--external"); + } + if (defined($opt_external) && !$opt_external) + { + @param = (@param, "--no-external"); + } + if (defined($opt_compat)) { + @param = (@param, "--compat", $opt_compat); + } + if (%opt_rc) { + foreach my $key (keys(%opt_rc)) { + @param = (@param, "--rc", "$key=".$opt_rc{$key}); + } + } + if (defined($opt_config_file)) { + @param = (@param, "--config-file", $opt_config_file); + } + system(@param) and exit($? >> 8); } @@ -834,7 +904,7 @@ sub read_file($) local $\; local *HANDLE; - open(HANDLE, "<$filename") || return undef; + open(HANDLE, "<", $filename) || return undef; $content = ; close(HANDLE); @@ -860,17 +930,21 @@ sub get_package($) local *HANDLE; info("Reading package $file:\n"); - info(" data directory .......: $dir\n"); $file = abs_path($file); chdir($dir); - open(HANDLE, "tar xvfz $file 2>/dev/null|") + open(HANDLE, "-|", "tar xvfz '$file' 2>/dev/null") or die("ERROR: could not process package $file\n"); + $count = 0; while () { if (/\.da$/ || /\.gcda$/) { $count++; } } close(HANDLE); + if ($count == 0) { + die("ERROR: no data file found in package $file\n"); + } + info(" data directory .......: $dir\n"); $build = read_file("$dir/$pkg_build_file"); if (defined($build)) { info(" build directory ......: $build\n"); @@ -904,7 +978,7 @@ sub write_file($$) my ($filename, $content) = @_; local *HANDLE; - open(HANDLE, ">$filename") || return 0; + open(HANDLE, ">", $filename) || return 0; print(HANDLE $content); close(HANDLE) || return 0; @@ -922,7 +996,7 @@ sub count_package_data($) local *HANDLE; my $count = 0; - open(HANDLE, "tar tfz $filename|") or return undef; + open(HANDLE, "-|", "tar tfz '$filename'") or return undef; while () { if (/\.da$/ || /\.gcda$/) { $count++; @@ -944,6 +1018,10 @@ sub create_package($$$;$) my ($file, $dir, $build, $gkv) = @_; my $cwd = getcwd(); + # Check for availability of tar tool first + system("tar --help > /dev/null") + and die("ERROR: tar command not available\n"); + # Print information about the package info("Creating package $file:\n"); info(" data directory .......: $dir\n"); @@ -972,6 +1050,7 @@ sub create_package($$$;$) chdir($dir); system("tar cfz $file .") and die("ERROR: could not create package $file\n"); + chdir($cwd); # Remove temporary files unlink("$dir/$pkg_build_file"); @@ -985,7 +1064,6 @@ sub create_package($$$;$) info(" data files ...........: $count\n"); } } - chdir($cwd); } sub find_link_fn($$$) @@ -1208,6 +1286,113 @@ sub kernel_capture() } # +# link_data_cb(datadir, rel, graphdir) +# +# Create symbolic link in GRAPDIR/REL pointing to DATADIR/REL. +# + +sub link_data_cb($$$) +{ + my ($datadir, $rel, $graphdir) = @_; + my $absfrom = catfile($datadir, $rel); + my $absto = catfile($graphdir, $rel); + my $base; + my $dir; + + if (-e $absto) { + die("ERROR: could not create symlink at $absto: ". + "File already exists!\n"); + } + if (-l $absto) { + # Broken link - possibly from an interrupted earlier run + unlink($absto); + } + + # Check for graph file + $base = $absto; + $base =~ s/\.(gcda|da)$//; + if (! -e $base.".gcno" && ! -e $base.".bbg" && ! -e $base.".bb") { + die("ERROR: No graph file found for $absfrom in ". + dirname($base)."!\n"); + } + + symlink($absfrom, $absto) or + die("ERROR: could not create symlink at $absto: $!\n"); +} + +# +# unlink_data_cb(datadir, rel, graphdir) +# +# Remove symbolic link from GRAPHDIR/REL to DATADIR/REL. +# + +sub unlink_data_cb($$$) +{ + my ($datadir, $rel, $graphdir) = @_; + my $absfrom = catfile($datadir, $rel); + my $absto = catfile($graphdir, $rel); + my $target; + + return if (!-l $absto); + $target = readlink($absto); + return if (!defined($target) || $target ne $absfrom); + + unlink($absto) or + warn("WARNING: could not remove symlink $absto: $!\n"); +} + +# +# link_data(datadir, graphdir, create) +# +# If CREATE is non-zero, create symbolic links in GRAPHDIR for data files +# found in DATADIR. Otherwise remove link in GRAPHDIR. +# + +sub link_data($$$) +{ + my ($datadir, $graphdir, $create) = @_; + + $datadir = abs_path($datadir); + $graphdir = abs_path($graphdir); + if ($create) { + lcov_find($datadir, \&link_data_cb, $graphdir, '\.gcda$', + '\.da$'); + } else { + lcov_find($datadir, \&unlink_data_cb, $graphdir, '\.gcda$', + '\.da$'); + } +} + +# +# find_graph_cb(datadir, rel, count_ref) +# +# Count number of files found. +# + +sub find_graph_cb($$$) +{ + my ($dir, $rel, $count_ref) = @_; + + ($$count_ref)++; +} + +# +# find_graph(dir) +# +# Search DIR for a graph file. Return non-zero if one was found, zero otherwise. +# + +sub find_graph($) +{ + my ($dir) = @_; + my $count = 0; + + lcov_find($dir, \&find_graph_cb, \$count, '\.gcno$', '\.bb$', '\.bbg$'); + + return $count > 0 ? 1 : 0; +} + +# # package_capture() # # Capture coverage data from a package of unprocessed coverage data files @@ -1242,7 +1427,16 @@ sub package_capture() } else { # Build directory needs to be passed to geninfo $base_directory = $build; - lcov_geninfo($dir); + if (find_graph($dir)) { + # Package contains graph files - collect from there + lcov_geninfo($dir); + } else { + # No graph files found, link data files next to + # graph files + link_data($dir, $base_directory, 1); + lcov_geninfo($base_directory); + link_data($dir, $base_directory, 0); + } } } @@ -1397,10 +1591,12 @@ sub br_ivec_push($$$$) my $i; $vec = "" if (!defined($vec)); + $block = $BR_VEC_MAX if $block < 0; # Check if branch already exists in vector for ($i = 0; $i < $num; $i++) { my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); + $v_block = $BR_VEC_MAX if $v_block < 0; next if ($v_block != $block || $v_branch != $branch); @@ -1437,6 +1633,7 @@ sub br_ivec_get($$) # Retrieve data from vector $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $block = -1 if ($block == $BR_VEC_MAX); $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); @@ -1492,6 +1689,10 @@ sub get_br_found_and_hit($) # "func" -> \%funcdata # "found" -> $lines_found (number of instrumented lines found in file) # "hit" -> $lines_hit (number of executed lines in file) +# "f_found" -> $fn_found (number of instrumented functions found in file) +# "f_hit" -> $fn_hit (number of executed functions in file) +# "b_found" -> $br_found (number of instrumented branches found in file) +# "b_hit" -> $br_hit (number of executed branches in file) # "check" -> \%checkdata # "testfnc" -> \%testfncdata # "sumfnc" -> \%sumfnccount @@ -1576,14 +1777,14 @@ sub read_info_file($) "compressed file $_[0]!\n"); # Open compressed file - open(INFO_HANDLE, "gunzip -c $_[0]|") + open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") or die("ERROR: cannot start gunzip to decompress ". "file $_[0]!\n"); } else { # Open decompressed file - open(INFO_HANDLE, $_[0]) + open(INFO_HANDLE, "<", $_[0]) or die("ERROR: cannot read file $_[0]!\n"); } @@ -1674,6 +1875,8 @@ sub read_info_file($) /^FN:(\d+),([^,]+)/ && do { + last if (!$func_coverage); + # Function data found, add to structure $funcdata->{$2} = $1; @@ -1692,6 +1895,8 @@ sub read_info_file($) /^FNDA:(\d+),([^,]+)/ && do { + last if (!$func_coverage); + # Function call count found, add to structure # Add summary counts $sumfnccount->{$2} += $1; @@ -1709,6 +1914,7 @@ sub read_info_file($) my ($line, $block, $branch, $taken) = ($1, $2, $3, $4); + last if (!$br_coverage); $sumbrcount->{$line} = br_ivec_push($sumbrcount->{$line}, $block, $branch, $taken); @@ -1912,8 +2118,8 @@ sub set_info_entry($$$$$$$$$;$$$$$$) sub add_counts($$) { - my %data1 = %{$_[0]}; # Hash 1 - my %data2 = %{$_[1]}; # Hash 2 + my $data1_ref = $_[0]; # Hash 1 + my $data2_ref = $_[1]; # Hash 2 my %result; # Resulting hash my $line; # Current line iteration scalar my $data1_count; # Count of line in hash1 @@ -1921,10 +2127,10 @@ sub add_counts($$) my $found = 0; # Total number of lines found my $hit = 0; # Number of lines with a count > 0 - foreach $line (keys(%data1)) + foreach $line (keys(%$data1_ref)) { - $data1_count = $data1{$line}; - $data2_count = $data2{$line}; + $data1_count = $data1_ref->{$line}; + $data2_count = $data2_ref->{$line}; # Add counts if present in both hashes if (defined($data2_count)) { $data1_count += $data2_count; } @@ -1936,14 +2142,14 @@ sub add_counts($$) if ($data1_count > 0) { $hit++; } } - # Add lines unique to data2 - foreach $line (keys(%data2)) + # Add lines unique to data2_ref + foreach $line (keys(%$data2_ref)) { - # Skip lines already in data1 - if (defined($data1{$line})) { next; } + # Skip lines already in data1_ref + if (defined($data1_ref->{$line})) { next; } - # Copy count from data2 - $result{$line} = $data2{$line}; + # Copy count from data2_ref + $result{$line} = $data2_ref->{$line}; $found++; if ($result{$line} > 0) { $hit++; } @@ -2439,7 +2645,7 @@ sub add_traces() if ($to_file) { info("Writing data to $output_filename\n"); - open(INFO_HANDLE, ">$output_filename") + open(INFO_HANDLE, ">", $output_filename) or die("ERROR: cannot write to $output_filename!\n"); @result = write_info_file(*INFO_HANDLE, $total_trace); close(*INFO_HANDLE); @@ -2548,6 +2754,7 @@ sub write_info_file(*$) my ($block, $branch, $taken) = br_ivec_get($brdata, $i); + $block = $BR_VEC_MAX if ($block < 0); print(INFO_HANDLE "BRDA:$line,$block,". "$branch,$taken\n"); $br_found++; @@ -2589,7 +2796,7 @@ sub write_info_file(*$) # # transform_pattern(pattern) # -# Transform shell wildcard expression to equivalent PERL regular expression. +# Transform shell wildcard expression to equivalent Perl regular expression. # Return transformed pattern. # @@ -2669,7 +2876,7 @@ sub extract() { info("Extracted $extracted files\n"); info("Writing data to $output_filename\n"); - open(INFO_HANDLE, ">$output_filename") + open(INFO_HANDLE, ">", $output_filename) or die("ERROR: cannot write to $output_filename!\n"); @result = write_info_file(*INFO_HANDLE, $data); close(*INFO_HANDLE); @@ -2725,7 +2932,7 @@ sub remove() { info("Deleted $removed files\n"); info("Writing data to $output_filename\n"); - open(INFO_HANDLE, ">$output_filename") + open(INFO_HANDLE, ">", $output_filename) or die("ERROR: cannot write to $output_filename!\n"); @result = write_info_file(*INFO_HANDLE, $data); close(*INFO_HANDLE); @@ -2836,13 +3043,13 @@ sub shorten_number($$) return '#'; } -sub shorten_rate($$) +sub shorten_rate($$$) { - my ($rate, $width) = @_; - my $result = sprintf("%*.1f%%", $width - 3, $rate); + my ($hit, $found, $width) = @_; + my $result = rate($hit, $found, "%", 1, $width); return $result if (length($result) <= $width); - $result = sprintf("%*d%%", $width - 1, $rate); + $result = rate($hit, $found, "%", 0, $width); return $result if (length($result) <= $width); return "#"; } @@ -3060,26 +3267,11 @@ sub list() $br_total_hit += $br_hit; # Determine line coverage rate for this file - if ($found == 0) { - $rate = "-"; - } else { - $rate = shorten_rate(100 * $hit / $found, - $fwidth[$F_LN_RATE]); - } + $rate = shorten_rate($hit, $found, $fwidth[$F_LN_RATE]); # Determine function coverage rate for this file - if (!defined($fn_found) || $fn_found == 0) { - $fnrate = "-"; - } else { - $fnrate = shorten_rate(100 * $fn_hit / $fn_found, - $fwidth[$F_FN_RATE]); - } + $fnrate = shorten_rate($fn_hit, $fn_found, $fwidth[$F_FN_RATE]); # Determine branch coverage rate for this file - if (!defined($br_found) || $br_found == 0) { - $brrate = "-"; - } else { - $brrate = shorten_rate(100 * $br_hit / $br_found, - $fwidth[$F_BR_RATE]); - } + $brrate = shorten_rate($br_hit, $br_found, $fwidth[$F_BR_RATE]); # Assemble line parameters push(@file_data, $print_filename); @@ -3095,26 +3287,13 @@ sub list() } # Determine total line coverage rate - if ($total_found == 0) { - $rate = "-"; - } else { - $rate = shorten_rate(100 * $total_hit / $total_found, - $fwidth[$F_LN_RATE]); - } + $rate = shorten_rate($total_hit, $total_found, $fwidth[$F_LN_RATE]); # Determine total function coverage rate - if ($fn_total_found == 0) { - $fnrate = "-"; - } else { - $fnrate = shorten_rate(100 * $fn_total_hit / $fn_total_found, - $fwidth[$F_FN_RATE]); - } + $fnrate = shorten_rate($fn_total_hit, $fn_total_found, + $fwidth[$F_FN_RATE]); # Determine total branch coverage rate - if ($br_total_found == 0) { - $brrate = "-"; - } else { - $brrate = shorten_rate(100 * $br_total_hit / $br_total_found, - $fwidth[$F_BR_RATE]); - } + $brrate = shorten_rate($br_total_hit, $br_total_found, + $fwidth[$F_BR_RATE]); # Print separator print(("="x$barlen)."\n"); @@ -3253,14 +3432,14 @@ sub read_diff($) "compressed file $diff_file!\n"); # Open compressed file - open(HANDLE, "gunzip -c $diff_file|") + open(HANDLE, "-|", "gunzip -c '$diff_file'") or die("ERROR: cannot start gunzip to decompress ". "file $_[0]!\n"); } else { # Open decompressed file - open(HANDLE, $diff_file) + open(HANDLE, "<", $diff_file) or die("ERROR: cannot read file $_[0]!\n"); } @@ -3668,10 +3847,10 @@ sub adjust_fncdata($$$) # Remove count data in testfncdata for functions which are no longer # in funcdata - foreach $testname (%{$testfncdata}) { + foreach $testname (keys(%{$testfncdata})) { my $fnccount = $testfncdata->{$testname}; - foreach $func (%{$fnccount}) { + foreach $func (keys(%{$fnccount})) { if (!defined($funcdata->{$func})) { delete($fnccount->{$func}); } @@ -3679,7 +3858,7 @@ sub adjust_fncdata($$$) } # Remove count data in sumfnccount for functions which are no longer # in funcdata - foreach $func (%{$sumfnccount}) { + foreach $func (keys(%{$sumfnccount})) { if (!defined($funcdata->{$func})) { delete($sumfnccount->{$func}); } @@ -3886,7 +4065,7 @@ sub diff() if ($to_file) { info("Writing data to $output_filename\n"); - open(INFO_HANDLE, ">$output_filename") + open(INFO_HANDLE, ">", $output_filename) or die("ERROR: cannot write to $output_filename!\n"); @result = write_info_file(*INFO_HANDLE, $trace_data); close(*INFO_HANDLE); @@ -3899,6 +4078,59 @@ sub diff() return @result; } +# +# summary() +# + +sub summary() +{ + my $filename; + my $current; + my $total; + my $ln_total_found; + my $ln_total_hit; + my $fn_total_found; + my $fn_total_hit; + my $br_total_found; + my $br_total_hit; + + # Read and combine trace files + foreach $filename (@opt_summary) { + $current = read_info_file($filename); + if (!defined($total)) { + $total = $current; + } else { + $total = combine_info_files($total, $current); + } + } + # Calculate coverage data + foreach $filename (keys(%{$total})) + { + my $entry = $total->{$filename}; + my $ln_found; + my $ln_hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + + (undef, undef, undef, undef, undef, undef, undef, undef, + $ln_found, $ln_hit, $fn_found, $fn_hit, $br_found, + $br_hit) = get_info_entry($entry); + + # Add to totals + $ln_total_found += $ln_found; + $ln_total_hit += $ln_hit; + $fn_total_found += $fn_found; + $fn_total_hit += $fn_hit; + $br_total_found += $br_found; + $br_total_hit += $br_hit; + } + + + return ($ln_total_found, $ln_total_hit, $fn_total_found, $fn_total_hit, + $br_total_found, $br_total_hit); +} # # system_no_output(mode, parameters) @@ -3920,12 +4152,12 @@ sub system_no_output($@) local *OLD_STDOUT; # Save old stdout and stderr handles - ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); - ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); + ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); # Redirect to /dev/null - ($mode & 1) && open(STDOUT, ">/dev/null"); - ($mode & 2) && open(STDERR, ">/dev/null"); + ($mode & 1) && open(STDOUT, ">", "/dev/null"); + ($mode & 2) && open(STDERR, ">", "/dev/null"); system(@_); $result = $?; @@ -3935,8 +4167,8 @@ sub system_no_output($@) ($mode & 2) && close(STDERR); # Restore old handles - ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); - ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); + ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); return $result; } @@ -3957,7 +4189,7 @@ sub read_config($) my $value; local *HANDLE; - if (!open(HANDLE, "<$filename")) + if (!open(HANDLE, "<", $filename)) { warn("WARNING: cannot read configuration file $filename\n"); return undef; @@ -3996,8 +4228,8 @@ sub read_config($) # key_string => var_ref # # where KEY_STRING is a keyword and VAR_REF is a reference to an associated -# variable. If the global configuration hash CONFIG contains a value for -# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# variable. If the global configuration hashes CONFIG or OPT_RC contain a value +# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. # sub apply_config($) @@ -4006,8 +4238,9 @@ sub apply_config($) foreach (keys(%{$ref})) { - if (defined($config->{$_})) - { + if (defined($opt_rc{$_})) { + ${$ref->{$_}} = $opt_rc{$_}; + } elsif (defined($config->{$_})) { ${$ref->{$_}} = $config->{$_}; } } @@ -4037,6 +4270,9 @@ sub abort_handler($) sub temp_cleanup() { + # Ensure temp directory is not in use by current process + chdir("/"); + if (@temp_dirs) { info("Removing temporary directories.\n"); foreach (@temp_dirs) { @@ -4145,8 +4381,8 @@ sub get_overall_line($$$$) return "no data found" if (!defined($found) || $found == 0); $name = ($found == 1) ? $name_sn : $name_pl; - return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit, - $found, $name); + + return rate($hit, $found, "% ($hit of $found $name)"); } @@ -4162,7 +4398,7 @@ sub print_overall_rate($$$$$$$$$) my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit, $br_do, $br_found, $br_hit) = @_; - info("Overall coverage rate:\n"); + info("Summary coverage rate:\n"); info(" lines......: %s\n", get_overall_line($ln_found, $ln_hit, "line", "lines")) if ($ln_do); @@ -4173,3 +4409,38 @@ sub print_overall_rate($$$$$$$$$) get_overall_line($br_found, $br_hit, "branch", "branches")) if ($br_do); } + + +# +# rate(hit, found[, suffix, precision, width]) +# +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. +# PRECISION specifies the precision of the result. SUFFIX defines a +# string that is appended to the result if FOUND is non-zero. Spaces +# are added to the start of the resulting string until it is at least WIDTH +# characters wide. +# + +sub rate($$;$$$) +{ + my ($hit, $found, $suffix, $precision, $width) = @_; + my $rate; + + # Assign defaults if necessary + $precision = 1 if (!defined($precision)); + $suffix = "" if (!defined($suffix)); + $width = 0 if (!defined($width)); + + return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); + $rate = sprintf("%.*f", $precision, $hit * 100 / $found); + + # Adjust rates if necessary + if ($rate == 0 && $hit > 0) { + $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); + } elsif ($rate == 100 && $hit != $found) { + $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); + } + + return sprintf("%*s", $width, $rate.$suffix); +} -- cgit v0.10.2-6-g49f6