diff options
Diffstat (limited to '3rdParty/LCov/geninfo')
-rwxr-xr-x | 3rdParty/LCov/geninfo | 1200 |
1 files changed, 967 insertions, 233 deletions
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<n>: Instrumentation flag for line n +# count<n>: Execution count for line n +# source<n>: Source code for line n +# +# funcdata: [ count1, func1, count2, func2, ... ] +# count<n>: Execution count for function number n +# func<n>: Function name for function number n +# +# bbdata: function_name -> [ line1, line2, ... ] +# line<n>: Line number belonging to the corresponding function +# + sub derive_data($$$) { my ($contentdata, $funcdata, $bbdata) = @_; @@ -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 "<built-in>.gcov"); + ($source, $object) = read_gcov_header($gcov_file); - if (defined($source)) - { - $source = solve_relative_path($base_dir, $source); + if (!defined($source)) { + # Derive source file name from gcov file name if + # header format could not be parsed + $source = $gcov_file; + $source =~ s/\.gcov$//; + } + + $source = solve_relative_path($base_dir, $source); + + if (defined($adjust_src_pattern)) { + # Apply transformation as specified by user + $source =~ s/$adjust_src_pattern/$adjust_src_replace/g; } # gcov will happily create output even if there's no source code # available - this interferes with checksum creation so we need # to pull the emergency brake here. - if (defined($source) && ! -r $source && $checksum) + if (! -r $source && $checksum) { if ($ignore[$ERROR_SOURCE]) { @@ -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; + } + } + # <exec count>:<line number>:<source code> 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 = <GCOV_PIPE>; + # 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 = <GCOV_PIPE>; + # 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 "<unknown>"; +} + +# +# parse_compat_modes(opt) +# +# Determine compatibility mode settings. +# + +sub parse_compat_modes($) +{ + my ($opt) = @_; + my @opt_list; + my %specified; + + # Initialize with defaults + %compat_value = %COMPAT_MODE_DEFAULTS; + + # Add old style specifications + if (defined($opt_compat_libtool)) { + $compat_value{$COMPAT_MODE_LIBTOOL} = + $opt_compat_libtool ? $COMPAT_VALUE_ON + : $COMPAT_VALUE_OFF; + } + + # Parse settings + if (defined($opt)) { + @opt_list = split(/\s*,\s*/, $opt); + } + foreach my $directive (@opt_list) { + my ($mode, $value); + + # Either + # mode=off|on|auto or + # mode (implies on) + if ($directive !~ /^(\w+)=(\w+)$/ && + $directive !~ /^(\w+)$/) { + die("ERROR: Unknown compatibility mode specification: ". + "$directive!\n"); + } + # Determine mode + $mode = $COMPAT_NAME_TO_MODE{lc($1)}; + if (!defined($mode)) { + die("ERROR: Unknown compatibility mode '$1'!\n"); + } + $specified{$mode} = 1; + # Determine value + if (defined($2)) { + $value = $COMPAT_NAME_TO_VALUE{lc($2)}; + if (!defined($value)) { + die("ERROR: Unknown compatibility mode ". + "value '$2'!\n"); + } + } else { + $value = $COMPAT_VALUE_ON; + } + $compat_value{$mode} = $value; + } + # Perform auto-detection + foreach my $mode (sort(keys(%compat_value))) { + my $value = $compat_value{$mode}; + my $is_autodetect = ""; + my $name = compat_name($mode); + + if ($value == $COMPAT_VALUE_AUTO) { + my $autodetect = $COMPAT_MODE_AUTO{$mode}; + + if (!defined($autodetect)) { + die("ERROR: No auto-detection for ". + "mode '$name' available!\n"); + } + + if (ref($autodetect) eq "CODE") { + $value = &$autodetect(); + $compat_value{$mode} = $value; + $is_autodetect = " (auto-detected)"; + } + } + + if ($specified{$mode}) { + if ($value == $COMPAT_VALUE_ON) { + info("Enabling compatibility mode ". + "'$name'$is_autodetect\n"); + } elsif ($value == $COMPAT_VALUE_OFF) { + info("Disabling compatibility mode ". + "'$name'$is_autodetect\n"); + } else { + info("Using delayed auto-detection for ". + "compatibility mode ". + "'$name'\n"); + } + } + } +} + +sub compat_hammer_autodetect() +{ + if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 || + $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302) + { + info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n"); + return $COMPAT_VALUE_ON; + } + return $COMPAT_VALUE_OFF; +} + +# +# is_compat(mode) +# +# Return non-zero if compatibility mode MODE is enabled. +# + +sub is_compat($) +{ + my ($mode) = @_; + + return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON); + return 0; +} + +# +# is_compat_auto(mode) +# +# Return non-zero if compatibility mode MODE is set to auto-detect. +# + +sub is_compat_auto($) +{ + my ($mode) = @_; + + return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO); + return 0; +} |