Index: third_party/lcov/bin/geninfo |
diff --git a/third_party/lcov/bin/geninfo b/third_party/lcov/bin/geninfo |
index 055641baccb7ac65ab8b8eaa62a20e3a3e33957a..9325f9d68c2d1cfb879d534a09951c70d12f9958 100755 |
--- a/third_party/lcov/bin/geninfo |
+++ b/third_party/lcov/bin/geninfo |
@@ -1,6 +1,6 @@ |
#!/usr/bin/perl -w |
# |
-# Copyright (c) International Business Machines Corp., 2002,2007 |
+# 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 |
@@ -51,16 +51,22 @@ |
use strict; |
use File::Basename; |
+use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir |
+ splitpath catpath/; |
use Getopt::Long; |
use Digest::MD5 qw(md5_base64); |
- |
+if( $^O eq "msys" ) |
+{ |
+ require File::Spec::Win32; |
+} |
# Constants |
-our $lcov_version = "LCOV version 1.7"; |
+our $lcov_version = 'LCOV version 1.10'; |
our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; |
our $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; |
@@ -68,41 +74,140 @@ 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"; |
+ |
+# 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; |
+our $BR_BRANCH = 2; |
+our $BR_TAKEN = 3; |
+our $BR_VEC_ENTRIES = 4; |
+our $BR_VEC_WIDTH = 32; |
+ |
+our $UNNAMED_BLOCK = 9999; |
# Prototypes |
sub print_usage(*); |
sub gen_info($); |
-sub process_dafile($); |
+sub process_dafile($$); |
sub match_filename($@); |
sub solve_ambiguous_match($$$); |
sub split_filename($); |
sub solve_relative_path($$); |
-sub get_dir($); |
sub read_gcov_header($); |
sub read_gcov_file($); |
-sub read_bb_file($$); |
-sub read_string(*$); |
-sub read_gcno_file($$); |
-sub read_gcno_string(*$); |
-sub read_hammer_bbg_file($$); |
-sub read_hammer_bbg_string(*$); |
-sub unpack_int32($$); |
sub info(@); |
sub get_gcov_version(); |
sub system_no_output($@); |
sub read_config($); |
sub apply_config($); |
-sub gen_initial_info($); |
-sub process_graphfile($); |
+sub get_exclusion_data($); |
+sub apply_exclusion_data($$); |
+sub process_graphfile($$); |
+sub filter_fn_name($); |
sub warn_handler($); |
sub die_handler($); |
+sub graph_error($$); |
+sub graph_expect($); |
+sub graph_read(*$;$$); |
+sub graph_skip(*$;$); |
+sub sort_uniq(@); |
+sub sort_uniq_lex(@); |
+sub graph_cleanup($); |
+sub graph_find_base($); |
+sub graph_from_bb($$$); |
+sub graph_add_order($$$); |
+sub read_bb_word(*;$); |
+sub read_bb_value(*;$); |
+sub read_bb_string(*$); |
+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_gcno_string(*$); |
+sub read_gcno_lines_record(*$$$$$$); |
+sub determine_gcno_split_crc($$$); |
+sub read_gcno_function_record(*$$$$); |
+sub read_gcno($); |
+sub get_gcov_capabilities(); |
+sub get_overall_line($$$$); |
+sub print_overall_rate($$$$$$$$$); |
+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; |
@@ -115,18 +220,35 @@ our $version; |
our $follow; |
our $checksum; |
our $no_checksum; |
-our $preserve_paths; |
-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; |
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 $cwd = `pwd`; |
chomp($cwd); |
@@ -141,8 +263,22 @@ $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 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"); |
+ |
# Read configuration file if available |
-if (-r $ENV{"HOME"}."/.lcovrc") |
+if (defined($opt_config_file)) { |
+ $config = read_config($opt_config_file); |
+} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) |
{ |
$config = read_config($ENV{"HOME"}."/.lcovrc"); |
} |
@@ -151,15 +287,23 @@ 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, |
+ }); |
# Merge options |
if (defined($no_checksum)) |
@@ -167,24 +311,53 @@ 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; |
+ } |
+ } |
} |
# Parse command line options |
-if (!GetOptions("test-name=s" => \$test_name, |
- "output-filename=s" => \$output_filename, |
+if (!GetOptions("test-name|t=s" => \$test_name, |
+ "output-filename|o=s" => \$output_filename, |
"checksum" => \$checksum, |
"no-checksum" => \$no_checksum, |
- "base-directory=s" => \$base_directory, |
- "version" =>\$version, |
- "quiet" => \$quiet, |
- "help|?" => \$help, |
- "follow" => \$follow, |
- "compat-libtool" => \$compat_libtool, |
- "no-compat-libtool" => \$no_compat_libtool, |
+ "base-directory|b=s" => \$base_directory, |
+ "version|v" =>\$version, |
+ "quiet|q" => \$quiet, |
+ "help|h|?" => \$help, |
+ "follow|f" => \$follow, |
+ "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, |
"no-recursion" => \$no_recursion, |
+ "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"); |
@@ -199,10 +372,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; |
} |
} |
@@ -222,6 +400,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) |
{ |
@@ -263,17 +465,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) |
{ |
@@ -302,41 +493,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; }; |
- 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"; |
@@ -353,19 +512,13 @@ else |
$graph_file_extension = ".gcno"; |
} |
-# Check for availability of --preserve-paths option of gcov |
-if (`$gcov_tool --help` =~ /--preserve-paths/) |
-{ |
- $preserve_paths = "--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); |
@@ -377,20 +530,20 @@ 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 |
-if ($initial) |
-{ |
- foreach (@data_directory) |
- { |
- gen_initial_info($_); |
- } |
+foreach my $entry (@data_directory) { |
+ gen_info($entry); |
} |
-else |
-{ |
- foreach (@data_directory) |
- { |
- gen_info($_); |
- } |
+ |
+if ($initial && $br_coverage) { |
+ warn("Note: --initial does not generate branch coverage ". |
+ "data\n"); |
} |
info("Finished .info-file creation\n"); |
@@ -426,15 +579,54 @@ sequentially. |
--(no-)checksum Enable (disable) line checksumming |
--(no-)compat-libtool Enable (disable) libtool compatibility mode |
--gcov-tool TOOL Specify gcov tool location |
- --ignore-errors ERROR Continue after ERROR (gcov, source) |
- --no-recursion Exlude subdirectories from processing |
- --function-coverage Capture function call counts |
+ --ignore-errors ERROR Continue after ERROR (gcov, source, graph) |
+ --no-recursion Exclude subdirectories from processing |
+ --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 |
; |
} |
+# |
+# get_common_prefix(min_dir, filenames) |
+# |
+# Return the longest path prefix shared by all filenames. MIN_DIR specifies |
+# the minimum number of directories that a filename may have after removing |
+# the prefix. |
+# |
+ |
+sub get_common_prefix($@) |
+{ |
+ my ($min_dir, @files) = @_; |
+ my $file; |
+ my @prefix; |
+ my $i; |
+ |
+ foreach $file (@files) { |
+ my ($v, $d, $f) = splitpath($file); |
+ my @comp = splitdir($d); |
+ |
+ if (!@prefix) { |
+ @prefix = @comp; |
+ next; |
+ } |
+ for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) { |
+ if ($comp[$i] ne $prefix[$i] || |
+ ((scalar(@comp) - ($i + 1)) <= $min_dir)) { |
+ delete(@prefix[$i..scalar(@prefix)]); |
+ last; |
+ } |
+ } |
+ } |
+ |
+ return catdir(@prefix); |
+} |
# |
# gen_info(directory) |
@@ -473,52 +665,190 @@ sub gen_info($) |
{ |
my $directory = $_[0]; |
my @file_list; |
+ my $file; |
+ my $prefix; |
+ my $type; |
+ my $ext; |
+ |
+ if ($initial) { |
+ $type = "graph"; |
+ $ext = $graph_file_extension; |
+ } else { |
+ $type = "data"; |
+ $ext = $data_file_extension; |
+ } |
if (-d $directory) |
{ |
- info("Scanning $directory for $data_file_extension ". |
- "files ...\n"); |
+ info("Scanning $directory for $ext files ...\n"); |
- @file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -type f 2>/dev/null`; |
+ @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`; |
chomp(@file_list); |
- @file_list or die("ERROR: no $data_file_extension files found ". |
- "in $directory!\n"); |
- info("Found %d data files in %s\n", $#file_list+1, $directory); |
+ @file_list or |
+ die("ERROR: no $ext files found in $directory!\n"); |
+ $prefix = get_common_prefix(1, @file_list); |
+ info("Found %d %s files in %s\n", $#file_list+1, $type, |
+ $directory); |
} |
else |
{ |
@file_list = ($directory); |
+ $prefix = ""; |
} |
# Process all files in list |
- foreach (@file_list) { process_dafile($_); } |
+ foreach $file (@file_list) { |
+ # Process file |
+ if ($initial) { |
+ process_graphfile($file, $prefix); |
+ } else { |
+ process_dafile($file, $prefix); |
+ } |
+ } |
} |
# |
-# process_dafile(da_filename) |
+# derive_data(contentdata, funcdata, bbdata) |
# |
-# Create a .info file for a single data file. |
+# 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) = @_; |
+ my @gcov_content = @{$contentdata}; |
+ my @gcov_functions = @{$funcdata}; |
+ my %fn_count; |
+ my %ln_fn; |
+ my $line; |
+ my $maxline; |
+ my %fn_name; |
+ my $fn; |
+ my $count; |
+ |
+ if (!defined($bbdata)) { |
+ return @gcov_functions; |
+ } |
+ |
+ # First add existing function data |
+ while (@gcov_functions) { |
+ $count = shift(@gcov_functions); |
+ $fn = shift(@gcov_functions); |
+ |
+ $fn_count{$fn} = $count; |
+ } |
+ |
+ # Convert line coverage data to function data |
+ foreach $fn (keys(%{$bbdata})) { |
+ my $line_data = $bbdata->{$fn}; |
+ my $line; |
+ my $fninstr = 0; |
+ |
+ if ($fn eq "") { |
+ next; |
+ } |
+ # 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; |
+ } |
+ |
+ |
+ # Check if we got data for all functions |
+ foreach $fn (keys(%fn_name)) { |
+ if ($fn eq "") { |
+ next; |
+ } |
+ if (defined($fn_count{$fn})) { |
+ next; |
+ } |
+ warn("WARNING: no derived data found for function $fn\n"); |
+ } |
+ |
+ # Convert hash to list in @gcov_functions format |
+ foreach $fn (sort(keys(%fn_count))) { |
+ push(@gcov_functions, $fn_count{$fn}, $fn); |
+ } |
+ |
+ return @gcov_functions; |
+} |
+ |
+# |
+# get_filenames(directory, pattern) |
+# |
+# Return a list of filenames found in directory which match the specified |
+# pattern. |
# |
# Die on error. |
# |
-sub process_dafile($) |
+sub get_filenames($$) |
{ |
- info("Processing %s\n", $_[0]); |
+ my ($dirname, $pattern) = @_; |
+ my @result; |
+ my $directory; |
+ local *DIR; |
+ |
+ opendir(DIR, $dirname) or |
+ die("ERROR: cannot read directory $dirname\n"); |
+ while ($directory = readdir(DIR)) { |
+ push(@result, $directory) if ($directory =~ /$pattern/); |
+ } |
+ closedir(DIR); |
+ |
+ return @result; |
+} |
+ |
+# |
+# process_dafile(da_filename, dir) |
+# |
+# Create a .info file for a single data file. |
+# |
+# Die on error. |
+# |
+sub process_dafile($$) |
+{ |
+ my ($file, $dir) = @_; |
my $da_filename; # Name of data file to process |
my $da_dir; # Directory of data file |
my $source_dir; # Directory of source file |
my $da_basename; # data filename without ".da/.gcda" extension |
my $bb_filename; # Name of respective graph file |
- my %bb_content; # Contents of graph file |
+ my $bb_basename; # Basename of the original graph file |
+ my $graph; # Contents of graph file |
+ my $instr; # Contents of graph file part 2 |
my $gcov_error; # Error code of gcov tool |
my $object_dir; # Directory containing all object files |
my $source_filename; # Name of a source code file |
my $gcov_file; # Name of a .gcov file |
my @gcov_content; # Content of a .gcov file |
- my @gcov_branches; # Branch content of a .gcov file |
+ my $gcov_branches; # Branch content of a .gcov file |
my @gcov_functions; # Function calls of a .gcov file |
my @gcov_list; # List of generated .gcov files |
my $line_number; # Line number count |
@@ -526,28 +856,31 @@ sub process_dafile($) |
my $lines_found; # Number of instrumented lines found |
my $funcs_hit; # Number of instrumented functions hit |
my $funcs_found; # Number of instrumented functions found |
+ my $br_hit; |
+ my $br_found; |
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; |
my $index; |
my $da_renamed; # If data file is to be renamed |
local *INFO_HANDLE; |
+ info("Processing %s\n", abs2rel($file, $dir)); |
# Get path to data file in absolute and normalized form (begins with /, |
# contains no more ../ or ./) |
- $da_filename = solve_relative_path($cwd, $_[0]); |
+ $da_filename = solve_relative_path($cwd, $file); |
# 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) |
@@ -577,7 +910,8 @@ sub process_dafile($) |
} |
# Construct name of graph file |
- $bb_filename = $da_dir."/".$da_basename.$graph_file_extension; |
+ $bb_basename = $da_basename.$graph_file_extension; |
+ $bb_filename = "$da_dir/$bb_basename"; |
# Find out the real location of graph file in case we're just looking at |
# a link |
@@ -601,21 +935,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)) |
{ |
- %bb_content = read_hammer_bbg_file($bb_filename, |
- $base_dir); |
+ ($instr, $graph) = read_bbg($bb_filename); |
} |
else |
{ |
- %bb_content = read_bb_file($bb_filename, $base_dir); |
+ ($instr, $graph) = read_bb($bb_filename); |
} |
} |
else |
{ |
- %bb_content = read_gcno_file($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. |
@@ -630,9 +970,21 @@ sub process_dafile($) |
"$object_dir/$da_basename$data_file_extension") |
and die ("ERROR: cannot create link $object_dir/". |
"$da_basename$data_file_extension!\n"); |
+ push(@tmp_links, |
+ "$object_dir/$da_basename$data_file_extension"); |
+ # Need to create link to graph file if basename of link |
+ # and file are different (CONFIG_MODVERSION compat) |
+ if ((basename($bb_filename) ne $bb_basename) && |
+ (! -e "$object_dir/$bb_basename")) { |
+ symlink($bb_filename, "$object_dir/$bb_basename") or |
+ warn("WARNING: cannot create link ". |
+ "$object_dir/$bb_basename\n"); |
+ push(@tmp_links, "$object_dir/$bb_basename"); |
+ } |
} |
# Change to directory containing data files and apply GCOV |
+ debug("chdir($base_dir)\n"); |
chdir($base_dir); |
if ($da_renamed) |
@@ -644,19 +996,8 @@ sub process_dafile($) |
} |
# Execute gcov command and suppress standard output |
- if ($preserve_paths) |
- { |
- $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
- "-o", $object_dir, |
- "--preserve-paths", |
- "-b"); |
- } |
- else |
- { |
- $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
- "-o", $object_dir, |
- "-b"); |
- } |
+ $gcov_error = system_no_output(1, $gcov_tool, $da_filename, |
+ "-o", $object_dir, @gcov_options); |
if ($da_renamed) |
{ |
@@ -664,10 +1005,9 @@ sub process_dafile($) |
and die ("ERROR: cannot rename $da_filename.ori"); |
} |
- # Clean up link |
- if ($object_dir ne $da_dir) |
- { |
- unlink($object_dir."/".$da_basename.$data_file_extension); |
+ # Clean up temporary links |
+ foreach (@tmp_links) { |
+ unlink($_); |
} |
if ($gcov_error) |
@@ -681,7 +1021,7 @@ sub process_dafile($) |
} |
# Collect data from resulting .gcov files and create .info file |
- @gcov_list = glob("*.gcov"); |
+ @gcov_list = get_filenames('.', '\.gcov$'); |
# Check for files |
if (!@gcov_list) |
@@ -700,7 +1040,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"); |
} |
@@ -708,7 +1048,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"); |
} |
@@ -717,20 +1057,35 @@ sub process_dafile($) |
# Traverse the list of generated .gcov files and combine them into a |
# single .info file |
- @unprocessed = keys(%bb_content); |
- foreach $gcov_file (@gcov_list) |
+ @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]) |
{ |
@@ -741,8 +1096,7 @@ sub process_dafile($) |
die("ERROR: could not read source file $source\n"); |
} |
- @matches = match_filename(defined($source) ? $source : |
- $gcov_file, keys(%bb_content)); |
+ @matches = match_filename($source, keys(%{$instr})); |
# Skip files that are not mentioned in the graph file |
if (!@matches) |
@@ -756,8 +1110,14 @@ sub process_dafile($) |
# Read in contents of gcov file |
@result = read_gcov_file($gcov_file); |
+ if (!defined($result[0])) { |
+ warn("WARNING: skipping unreadable file ". |
+ $gcov_file."\n"); |
+ unlink($gcov_file); |
+ next; |
+ } |
@gcov_content = @{$result[0]}; |
- @gcov_branches = @{$result[1]}; |
+ $gcov_branches = $result[1]; |
@gcov_functions = @{$result[2]}; |
# Skip empty files |
@@ -790,22 +1150,61 @@ sub process_dafile($) |
} |
} |
+ # 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; |
+ } |
+ } |
+ |
# Write absolute path of source file |
printf(INFO_HANDLE "SF:%s\n", $source_filename); |
+ # If requested, derive function coverage data from |
+ # line coverage data of the first line of a function |
+ if ($opt_derive_func_data) { |
+ @gcov_functions = |
+ derive_data(\@gcov_content, \@gcov_functions, |
+ $graph->{$source_filename}); |
+ } |
+ |
# Write function-related information |
- if (defined($bb_content{$source_filename})) |
+ if (defined($graph->{$source_filename})) |
{ |
- foreach (split(",",$bb_content{$source_filename})) |
- { |
- my ($fn, $line) = split("=", $_); |
+ my $fn_data = $graph->{$source_filename}; |
+ my $fn; |
+ foreach $fn (sort |
+ {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]} |
+ keys(%{$fn_data})) { |
+ my $ln_data = $fn_data->{$fn}; |
+ my $line = $ln_data->[0]; |
+ |
+ # Skip empty function |
if ($fn eq "") { |
next; |
} |
+ # Remove excluded functions |
+ if (!$no_markers) { |
+ my $gfn; |
+ my $found = 0; |
+ |
+ foreach $gfn (@gcov_functions) { |
+ if ($gfn eq $fn) { |
+ $found = 1; |
+ last; |
+ } |
+ } |
+ if (!$found) { |
+ next; |
+ } |
+ } |
# Normalize function name |
- $fn =~ s/\W/_/g; |
+ $fn = filter_fn_name($fn); |
print(INFO_HANDLE "FN:$line,$fn\n"); |
} |
@@ -820,18 +1219,42 @@ sub process_dafile($) |
$funcs_hit = 0; |
while (@gcov_functions) |
{ |
- printf(INFO_HANDLE "FNDA:%s,%s\n", |
- $gcov_functions[0], |
- $gcov_functions[1]); |
- $funcs_found++; |
- $funcs_hit++ if $gcov_functions[0]; |
- splice(@gcov_functions,0,2); |
+ my $count = shift(@gcov_functions); |
+ my $fn = shift(@gcov_functions); |
+ |
+ $fn = filter_fn_name($fn); |
+ printf(INFO_HANDLE "FNDA:$count,$fn\n"); |
+ $funcs_found++; |
+ $funcs_hit++ if ($count > 0); |
} |
if ($funcs_found > 0) { |
printf(INFO_HANDLE "FNF:%s\n", $funcs_found); |
printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); |
} |
+ # Write coverage information for each instrumented branch: |
+ # |
+ # BRDA:<line number>,<block number>,<branch number>,<taken> |
+ # |
+ # where 'taken' is the number of times the branch was taken |
+ # or '-' if the block to which the branch belongs was never |
+ # executed |
+ $br_found = 0; |
+ $br_hit = 0; |
+ $num = br_gvec_len($gcov_branches); |
+ for ($i = 0; $i < $num; $i++) { |
+ my ($line, $block, $branch, $taken) = |
+ br_gvec_get($gcov_branches, $i); |
+ |
+ print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); |
+ $br_found++; |
+ $br_hit++ if ($taken ne '-' && $taken > 0); |
+ } |
+ if ($br_found > 0) { |
+ printf(INFO_HANDLE "BRF:%s\n", $br_found); |
+ printf(INFO_HANDLE "BRH:%s\n", $br_hit); |
+ } |
+ |
# Reset line counters |
$line_number = 0; |
$lines_found = 0; |
@@ -862,27 +1285,6 @@ sub process_dafile($) |
splice(@gcov_content,0,3); |
} |
- #-- |
- #-- BA: <code-line>, <branch-coverage> |
- #-- |
- #-- print one BA line for every branch of a |
- #-- conditional. <branch-coverage> values |
- #-- are: |
- #-- 0 - not executed |
- #-- 1 - executed but not taken |
- #-- 2 - executed and taken |
- #-- |
- while (@gcov_branches) |
- { |
- if ($gcov_branches[0]) |
- { |
- printf(INFO_HANDLE "BA:%s,%s\n", |
- $gcov_branches[0], |
- $gcov_branches[1]); |
- } |
- splice(@gcov_branches,0,2); |
- } |
- |
# Write line statistics and section separator |
printf(INFO_HANDLE "LF:%s\n", $lines_found); |
printf(INFO_HANDLE "LH:%s\n", $lines_hit); |
@@ -922,8 +1324,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 =~ /^[^\/]/) |
@@ -936,6 +1370,10 @@ sub solve_relative_path($$) |
# Remove . |
$result =~ s/\/\.\//\//g; |
+ $result =~ s/\/\.$/\//g; |
+ |
+ # Remove trailing / |
+ $result =~ s/\/$//g; |
# Solve .. |
while ($result =~ s/\/[^\/]+\/\.\.\//\//) |
@@ -958,28 +1396,42 @@ sub solve_relative_path($$) |
sub match_filename($@) |
{ |
- my $filename = shift; |
- my @list = @_; |
+ my ($filename, @list) = @_; |
+ my ($vol, $dir, $file) = splitpath($filename); |
+ my @comp = splitdir($dir); |
+ my $comps = scalar(@comp); |
+ my $entry; |
my @result; |
- $filename =~ s/^(.*).gcov$/$1/; |
- |
- if ($filename =~ /^\/(.*)$/) |
- { |
- $filename = "$1"; |
- } |
+entry: |
+ foreach $entry (@list) { |
+ my ($evol, $edir, $efile) = splitpath($entry); |
+ my @ecomp; |
+ my $ecomps; |
+ my $i; |
- foreach (@list) |
- { |
- if (/\/\Q$filename\E(.*)$/ && $1 eq "") |
- { |
- @result = (@result, $_); |
+ # Filename component must match |
+ if ($efile ne $file) { |
+ next; |
+ } |
+ # Check directory components last to first for match |
+ @ecomp = splitdir($edir); |
+ $ecomps = scalar(@ecomp); |
+ if ($ecomps < $comps) { |
+ next; |
+ } |
+ for ($i = 0; $i < $comps; $i++) { |
+ if ($comp[$comps - $i - 1] ne |
+ $ecomp[$ecomps - $i - 1]) { |
+ next entry; |
+ } |
} |
+ push(@result, $entry), |
} |
+ |
return @result; |
} |
- |
# |
# solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) |
# |
@@ -1006,7 +1458,7 @@ sub solve_ambiguous_match($$$) |
{ |
# Compare file contents |
- open(SOURCE, $filename) |
+ open(SOURCE, "<", $filename) |
or die("ERROR: cannot read $filename!\n"); |
$no_match = 0; |
@@ -1014,6 +1466,9 @@ sub solve_ambiguous_match($$$) |
{ |
chomp; |
+ # Also remove CR from line-end |
+ s/\015$//; |
+ |
if ($_ ne @$content[$index]) |
{ |
$no_match = 1; |
@@ -1052,21 +1507,6 @@ sub split_filename($) |
# |
-# get_dir(filename); |
-# |
-# Return the directory component of a given FILENAME. |
-# |
- |
-sub get_dir($) |
-{ |
- my @components = split("/", $_[0]); |
- pop(@components); |
- |
- return join("/", @components); |
-} |
- |
- |
-# |
# read_gcov_header(gcov_filename) |
# |
# Parse file GCOV_FILENAME and return a list containing the following |
@@ -1088,7 +1528,7 @@ sub read_gcov_header($) |
my $object; |
local *INPUT; |
- if (!open(INPUT, $_[0])) |
+ if (!open(INPUT, "<", $_[0])) |
{ |
if ($ignore_errors[$ERROR_GCOV]) |
{ |
@@ -1102,6 +1542,9 @@ sub read_gcov_header($) |
{ |
chomp($_); |
+ # Also remove CR from line-end |
+ s/\015$//; |
+ |
if (/^\s+-:\s+0:Source:(.*)$/) |
{ |
# Source: header entry |
@@ -1125,6 +1568,84 @@ sub read_gcov_header($) |
# |
+# br_gvec_len(vector) |
+# |
+# Return the number of entries in the branch coverage vector. |
+# |
+ |
+sub br_gvec_len($) |
+{ |
+ my ($vec) = @_; |
+ |
+ return 0 if (!defined($vec)); |
+ return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; |
+} |
+ |
+ |
+# |
+# br_gvec_get(vector, number) |
+# |
+# Return an entry from the branch coverage vector. |
+# |
+ |
+sub br_gvec_get($$) |
+{ |
+ my ($vec, $num) = @_; |
+ my $line; |
+ my $block; |
+ my $branch; |
+ my $taken; |
+ my $offset = $num * $BR_VEC_ENTRIES; |
+ |
+ # Retrieve data from vector |
+ $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); |
+ $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); |
+ $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); |
+ $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); |
+ |
+ # Decode taken value from an integer |
+ if ($taken == 0) { |
+ $taken = "-"; |
+ } else { |
+ $taken--; |
+ } |
+ |
+ return ($line, $block, $branch, $taken); |
+} |
+ |
+ |
+# |
+# br_gvec_push(vector, line, block, branch, taken) |
+# |
+# Add an entry to the branch coverage vector. |
+# |
+ |
+sub br_gvec_push($$$$$) |
+{ |
+ my ($vec, $line, $block, $branch, $taken) = @_; |
+ my $offset; |
+ |
+ $vec = "" if (!defined($vec)); |
+ $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; |
+ |
+ # Encode taken value into an integer |
+ if ($taken eq "-") { |
+ $taken = 0; |
+ } else { |
+ $taken++; |
+ } |
+ |
+ # Add to vector |
+ vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line; |
+ vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; |
+ vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; |
+ vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; |
+ |
+ return $vec; |
+} |
+ |
+ |
+# |
# read_gcov_file(gcov_filename) |
# |
# Parse file GCOV_FILENAME (.gcov file format) and return the list: |
@@ -1137,8 +1658,8 @@ sub read_gcov_header($) |
# $result[($line_number-1)*3+1] = execution count for line $line_number |
# $result[($line_number-1)*3+2] = source code text for line $line_number |
# |
-# gcov_branch is a list of 2 elements |
-# (linenumber, branch result) for each branch |
+# gcov_branch is a vector of 4 4-byte long elements for each branch: |
+# line number, block number, branch number, count + 1 or 0 |
# |
# gcov_func is a list of 2 elements |
# (number of calls, function name) for each function |
@@ -1150,13 +1671,23 @@ sub read_gcov_file($) |
{ |
my $filename = $_[0]; |
my @result = (); |
- my @branches = (); |
+ my $branches = ""; |
my @functions = (); |
my $number; |
+ my $exclude_flag = 0; |
+ my $exclude_line = 0; |
+ my $last_block = $UNNAMED_BLOCK; |
+ my $last_line = 0; |
local *INPUT; |
- open(INPUT, $filename) |
- or die("ERROR: cannot read $filename!\n"); |
+ if (!open(INPUT, "<", $filename)) { |
+ if ($ignore_errors[$ERROR_GCOV]) |
+ { |
+ warn("WARNING: cannot read $filename!\n"); |
+ return (undef, undef, undef); |
+ } |
+ die("ERROR: cannot read $filename!\n"); |
+ } |
if ($gcov_version < $GCOV_VERSION_3_3_0) |
{ |
@@ -1165,29 +1696,19 @@ sub read_gcov_file($) |
{ |
chomp($_); |
- if (/^\t\t(.*)$/) |
- { |
- # Uninstrumented line |
- push(@result, 0); |
- push(@result, 0); |
- push(@result, $1); |
- } |
- elsif (/^branch/) |
- { |
- # Branch execution data |
- push(@branches, scalar(@result) / 3); |
- if (/^branch \d+ never executed$/) |
- { |
- push(@branches, 0); |
- } |
- elsif (/^branch \d+ taken = 0%/) |
- { |
- push(@branches, 1); |
- } |
- else |
- { |
- push(@branches, 2); |
- } |
+ # Also remove CR from line-end |
+ s/\015$//; |
+ |
+ if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { |
+ next if (!$br_coverage); |
+ next if ($exclude_line); |
+ $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); |
+ $branches = br_gvec_push($branches, $last_line, |
+ $last_block, $1, '-'); |
} |
elsif (/^call/ || /^function/) |
{ |
@@ -1195,15 +1716,43 @@ sub read_gcov_file($) |
} |
else |
{ |
+ $last_line++; |
+ # Check for exclusion markers |
+ if (!$no_markers) { |
+ if (/$EXCL_STOP/) { |
+ $exclude_flag = 0; |
+ } elsif (/$EXCL_START/) { |
+ $exclude_flag = 1; |
+ } |
+ if (/$EXCL_LINE/ || $exclude_flag) { |
+ $exclude_line = 1; |
+ } else { |
+ $exclude_line = 0; |
+ } |
+ } |
# Source code execution data |
- $number = (split(" ",substr($_, 0, 16)))[0]; |
+ if (/^\t\t(.*)$/) |
+ { |
+ # Uninstrumented line |
+ push(@result, 0); |
+ push(@result, 0); |
+ push(@result, $1); |
+ next; |
+ } |
+ $number = (split(" ",substr($_, 0, 16)))[0]; |
# Check for zero count which is indicated |
# by ###### |
if ($number eq "######") { $number = 0; } |
- push(@result, 1); |
- push(@result, $number); |
+ if ($exclude_line) { |
+ # Register uninstrumented line instead |
+ push(@result, 0); |
+ push(@result, 0); |
+ } else { |
+ push(@result, 1); |
+ push(@result, $number); |
+ } |
push(@result, substr($_, 16)); |
} |
} |
@@ -1215,25 +1764,31 @@ sub read_gcov_file($) |
{ |
chomp($_); |
- if (/^branch\s+\d+\s+(\S+)\s+(\S+)/) |
- { |
- # Branch execution data |
- push(@branches, scalar(@result) / 3); |
- if ($1 eq "never") |
- { |
- push(@branches, 0); |
- } |
- elsif ($2 eq "0%") |
- { |
- push(@branches, 1); |
- } |
- else |
- { |
- push(@branches, 2); |
- } |
+ # Also remove CR from line-end |
+ s/\015$//; |
+ |
+ if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) { |
+ # Block information - used to group related |
+ # branches |
+ $last_line = $2; |
+ $last_block = $3; |
+ } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { |
+ next if (!$br_coverage); |
+ next if ($exclude_line); |
+ $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); |
+ $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; |
+ } |
push(@functions, $2, $1); |
} |
elsif (/^call/) |
@@ -1242,580 +1797,59 @@ sub read_gcov_file($) |
} |
elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) |
{ |
+ my ($count, $line, $code) = ($1, $2, $3); |
+ |
+ $last_line = $line; |
+ $last_block = $UNNAMED_BLOCK; |
+ # Check for exclusion markers |
+ if (!$no_markers) { |
+ if (/$EXCL_STOP/) { |
+ $exclude_flag = 0; |
+ } elsif (/$EXCL_START/) { |
+ $exclude_flag = 1; |
+ } |
+ if (/$EXCL_LINE/ || $exclude_flag) { |
+ $exclude_line = 1; |
+ } else { |
+ $exclude_line = 0; |
+ } |
+ } |
# <exec count>:<line number>:<source code> |
- if ($2 eq "0") |
+ if ($line eq "0") |
{ |
# Extra data |
} |
- elsif ($1 eq "-") |
+ elsif ($count eq "-") |
{ |
# Uninstrumented line |
push(@result, 0); |
push(@result, 0); |
- push(@result, $3); |
+ push(@result, $code); |
} |
else |
{ |
- # Source code execution data |
- $number = $1; |
- |
- # Check for zero count |
- if ($number eq "#####") { $number = 0; } |
- |
- push(@result, 1); |
- push(@result, $number); |
- push(@result, $3); |
- } |
- } |
- } |
- } |
- |
- close(INPUT); |
- return(\@result, \@branches, \@functions); |
-} |
- |
- |
-# |
-# read_bb_file(bb_filename, base_dir) |
-# |
-# Read .bb file BB_FILENAME and return a hash containing the following |
-# mapping: |
-# |
-# filename -> comma-separated list of pairs (function name=starting |
-# line number) to indicate the starting line of a function or |
-# =name to indicate an instrumented line |
-# |
-# for each entry in the .bb file. Filenames are absolute, i.e. relative |
-# filenames are prefixed with BASE_DIR. |
-# |
-# Die on error. |
-# |
- |
-sub read_bb_file($$) |
-{ |
- my $bb_filename = $_[0]; |
- my $base_dir = $_[1]; |
- my %result; |
- my $filename; |
- my $function_name; |
- my $minus_one = sprintf("%d", 0x80000001); |
- my $minus_two = sprintf("%d", 0x80000002); |
- my $value; |
- my $packed_word; |
- local *INPUT; |
- |
- open(INPUT, $bb_filename) |
- or die("ERROR: cannot read $bb_filename!\n"); |
- |
- binmode(INPUT); |
- |
- # Read data in words of 4 bytes |
- while (read(INPUT, $packed_word, 4) == 4) |
- { |
- # Decode integer in intel byteorder |
- $value = unpack_int32($packed_word, 0); |
- |
- # Note: the .bb file format is documented in GCC info pages |
- if ($value == $minus_one) |
- { |
- # Filename follows |
- $filename = read_string(*INPUT, $minus_one) |
- or die("ERROR: incomplete filename in ". |
- "$bb_filename!\n"); |
- |
- # Make path absolute |
- $filename = solve_relative_path($base_dir, $filename); |
- |
- # Insert into hash if not yet present. |
- # This is necessary because functions declared as |
- # "inline" are not listed as actual functions in |
- # .bb files |
- if (!$result{$filename}) |
- { |
- $result{$filename}=""; |
- } |
- } |
- elsif ($value == $minus_two) |
- { |
- # Function name follows |
- $function_name = read_string(*INPUT, $minus_two) |
- or die("ERROR: incomplete function ". |
- "name in $bb_filename!\n"); |
- $function_name =~ s/\W/_/g; |
- } |
- elsif ($value > 0) |
- { |
- if (defined($filename)) |
- { |
- $result{$filename} .= |
- ($result{$filename} ? "," : ""). |
- "=$value"; |
- } |
- else |
- { |
- warn("WARNING: unassigned line". |
- " number in .bb file ". |
- "$bb_filename\n"); |
- } |
- if ($function_name) |
- { |
- # Got a full entry filename, funcname, lineno |
- # Add to resulting hash |
- |
- $result{$filename}.= |
- ($result{$filename} ? "," : ""). |
- join("=",($function_name,$value)); |
- undef($function_name); |
- } |
- } |
- } |
- close(INPUT); |
- |
- if (!scalar(keys(%result))) |
- { |
- die("ERROR: no data found in $bb_filename!\n"); |
- } |
- return %result; |
-} |
- |
- |
-# |
-# read_string(handle, delimiter); |
-# |
-# Read and return a string in 4-byte chunks from HANDLE until DELIMITER |
-# is found. |
-# |
-# Return empty string on error. |
-# |
- |
-sub read_string(*$) |
-{ |
- my $HANDLE = $_[0]; |
- my $delimiter = $_[1]; |
- my $string = ""; |
- my $packed_word; |
- my $value; |
- |
- while (read($HANDLE,$packed_word,4) == 4) |
- { |
- $value = unpack_int32($packed_word, 0); |
- |
- if ($value == $delimiter) |
- { |
- # Remove trailing nil bytes |
- $/="\0"; |
- while (chomp($string)) {}; |
- $/="\n"; |
- return($string); |
- } |
- |
- $string = $string.$packed_word; |
- } |
- return(""); |
-} |
- |
- |
-# |
-# read_gcno_file(bb_filename, base_dir) |
-# |
-# Read .gcno file BB_FILENAME and return a hash containing the following |
-# mapping: |
-# |
-# filename -> comma-separated list of pairs (function name=starting |
-# line number) to indicate the starting line of a function or |
-# =name to indicate an instrumented line |
-# |
-# for each entry in the .gcno file. Filenames are absolute, i.e. relative |
-# filenames are prefixed with BASE_DIR. |
-# |
-# Die on error. |
-# |
- |
-sub read_gcno_file($$) |
-{ |
- my $gcno_filename = $_[0]; |
- my $base_dir = $_[1]; |
- my %result; |
- my $filename; |
- my $function_name; |
- my $lineno; |
- my $length; |
- my $value; |
- my $endianness; |
- my $blocks; |
- my $packed_word; |
- my $string; |
- local *INPUT; |
- |
- open(INPUT, $gcno_filename) |
- or die("ERROR: cannot read $gcno_filename!\n"); |
- |
- binmode(INPUT); |
- |
- read(INPUT, $packed_word, 4) == 4 |
- or die("ERROR: Invalid gcno file format\n"); |
- |
- $value = unpack_int32($packed_word, 0); |
- $endianness = !($value == $GCNO_FILE_MAGIC); |
- |
- unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC |
- or die("ERROR: gcno file magic does not match\n"); |
- |
- seek(INPUT, 8, 1); |
- |
- # Read data in words of 4 bytes |
- while (read(INPUT, $packed_word, 4) == 4) |
- { |
- # Decode integer in intel byteorder |
- $value = unpack_int32($packed_word, $endianness); |
- |
- if ($value == $GCNO_FUNCTION_TAG) |
- { |
- # skip length, ident and checksum |
- seek(INPUT, 12, 1); |
- (undef, $function_name) = |
- read_gcno_string(*INPUT, $endianness); |
- $function_name =~ s/\W/_/g; |
- (undef, $filename) = |
- read_gcno_string(*INPUT, $endianness); |
- $filename = solve_relative_path($base_dir, $filename); |
- |
- read(INPUT, $packed_word, 4); |
- $lineno = unpack_int32($packed_word, $endianness); |
- |
- $result{$filename}.= |
- ($result{$filename} ? "," : ""). |
- join("=",($function_name,$lineno)); |
- } |
- elsif ($value == $GCNO_LINES_TAG) |
- { |
- # Check for names of files containing inlined code |
- # included in this file |
- read(INPUT, $packed_word, 4); |
- $length = unpack_int32($packed_word, $endianness); |
- if ($length > 0) |
- { |
- # Block number |
- read(INPUT, $packed_word, 4); |
- $length--; |
- } |
- while ($length > 0) |
- { |
- read(INPUT, $packed_word, 4); |
- $lineno = unpack_int32($packed_word, |
- $endianness); |
- $length--; |
- if ($lineno != 0) |
- { |
- if (defined($filename)) |
- { |
- $result{$filename} .= |
- ($result{$filename} ? "," : ""). |
- "=$lineno"; |
- } |
- else |
- { |
- warn("WARNING: unassigned line". |
- " number in .gcno file ". |
- "$gcno_filename\n"); |
- } |
- next; |
- } |
- last if ($length == 0); |
- ($blocks, $string) = |
- read_gcno_string(*INPUT, $endianness); |
- if (defined($string)) |
- { |
- $filename = $string; |
- } |
- if ($blocks > 1) |
- { |
- $filename = solve_relative_path( |
- $base_dir, $filename); |
- if (!defined($result{$filename})) |
- { |
- $result{$filename} = ""; |
+ if ($exclude_line) { |
+ push(@result, 0); |
+ push(@result, 0); |
+ } else { |
+ # Check for zero count |
+ if ($count eq "#####") { |
+ $count = 0; |
+ } |
+ push(@result, 1); |
+ push(@result, $count); |
} |
+ push(@result, $code); |
} |
- $length -= $blocks; |
} |
} |
- else |
- { |
- read(INPUT, $packed_word, 4); |
- $length = unpack_int32($packed_word, $endianness); |
- seek(INPUT, 4 * $length, 1); |
- } |
- } |
- close(INPUT); |
- |
- if (!scalar(keys(%result))) |
- { |
- die("ERROR: no data found in $gcno_filename!\n"); |
- } |
- return %result; |
-} |
- |
- |
-# |
-# read_gcno_string(handle, endianness); |
-# |
-# Read a string in 4-byte chunks from HANDLE. |
-# |
-# Return (number of 4-byte chunks read, string). |
-# |
- |
-sub read_gcno_string(*$) |
-{ |
- my $handle = $_[0]; |
- my $endianness = $_[1]; |
- my $number_of_blocks = 0; |
- my $string = ""; |
- my $packed_word; |
- |
- read($handle, $packed_word, 4) == 4 |
- or die("ERROR: reading string\n"); |
- |
- $number_of_blocks = unpack_int32($packed_word, $endianness); |
- |
- if ($number_of_blocks == 0) |
- { |
- return (1, undef); |
- } |
- |
- if (read($handle, $packed_word, 4 * $number_of_blocks) != |
- 4 * $number_of_blocks) |
- { |
- my $msg = "invalid string size ".(4 * $number_of_blocks)." in ". |
- "gcno file at position ".tell($handle)."\n"; |
- if ($ignore[$ERROR_SOURCE]) |
- { |
- warn("WARNING: $msg"); |
- return (1, undef); |
- } |
- else |
- { |
- die("ERROR: $msg"); |
- } |
} |
- $string = $string . $packed_word; |
- |
- # Remove trailing nil bytes |
- $/="\0"; |
- while (chomp($string)) {}; |
- $/="\n"; |
- |
- return(1 + $number_of_blocks, $string); |
-} |
- |
- |
-# |
-# read_hammer_bbg_file(bb_filename, base_dir) |
-# |
-# Read .bbg file BB_FILENAME and return a hash containing the following |
-# mapping: |
-# |
-# filename -> comma-separated list of pairs (function name=starting |
-# line number) to indicate the starting line of a function or |
-# =name to indicate an instrumented line |
-# |
-# for each entry in the .bbg file. Filenames are absolute, i.e. relative |
-# filenames are prefixed with BASE_DIR. |
-# |
-# Die on error. |
-# |
- |
-sub read_hammer_bbg_file($$) |
-{ |
- my $bbg_filename = $_[0]; |
- my $base_dir = $_[1]; |
- my %result; |
- my $filename; |
- my $function_name; |
- my $first_line; |
- my $lineno; |
- my $length; |
- my $value; |
- my $endianness; |
- my $blocks; |
- my $packed_word; |
- local *INPUT; |
- |
- open(INPUT, $bbg_filename) |
- or die("ERROR: cannot read $bbg_filename!\n"); |
- |
- binmode(INPUT); |
- |
- # Read magic |
- read(INPUT, $packed_word, 4) == 4 |
- or die("ERROR: invalid bbg file format\n"); |
- |
- $endianness = 1; |
- |
- unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC |
- or die("ERROR: bbg file magic does not match\n"); |
- |
- # Skip version |
- seek(INPUT, 4, 1); |
- |
- # Read data in words of 4 bytes |
- while (read(INPUT, $packed_word, 4) == 4) |
- { |
- # Get record tag |
- $value = unpack_int32($packed_word, $endianness); |
- |
- # Get record length |
- read(INPUT, $packed_word, 4); |
- $length = unpack_int32($packed_word, $endianness); |
- |
- if ($value == $GCNO_FUNCTION_TAG) |
- { |
- # Get function name |
- ($value, $function_name) = |
- read_hammer_bbg_string(*INPUT, $endianness); |
- $function_name =~ s/\W/_/g; |
- $filename = undef; |
- $first_line = undef; |
- |
- seek(INPUT, $length - $value * 4, 1); |
- } |
- elsif ($value == $GCNO_LINES_TAG) |
- { |
- # Get linenumber and filename |
- # Skip block number |
- seek(INPUT, 4, 1); |
- $length -= 4; |
- |
- while ($length > 0) |
- { |
- read(INPUT, $packed_word, 4); |
- $lineno = unpack_int32($packed_word, |
- $endianness); |
- $length -= 4; |
- if ($lineno != 0) |
- { |
- if (!defined($first_line)) |
- { |
- $first_line = $lineno; |
- } |
- if (defined($filename)) |
- { |
- $result{$filename} .= |
- ($result{$filename} ? "," : ""). |
- "=$lineno"; |
- } |
- else |
- { |
- warn("WARNING: unassigned line". |
- " number in .bbg file ". |
- "$bbg_filename\n"); |
- } |
- next; |
- } |
- ($blocks, $value) = |
- read_hammer_bbg_string( |
- *INPUT, $endianness); |
- # Add all filenames to result list |
- if (defined($value)) |
- { |
- $value = solve_relative_path( |
- $base_dir, $value); |
- if (!defined($result{$value})) |
- { |
- $result{$value} = undef; |
- } |
- if (!defined($filename)) |
- { |
- $filename = $value; |
- } |
- } |
- $length -= $blocks * 4; |
- |
- # Got a complete data set? |
- if (defined($filename) && |
- defined($first_line) && |
- defined($function_name)) |
- { |
- # Add it to our result hash |
- if (defined($result{$filename})) |
- { |
- $result{$filename} .= |
- ",$function_name=$first_line"; |
- } |
- else |
- { |
- $result{$filename} = |
- "$function_name=$first_line"; |
- } |
- $function_name = undef; |
- $filename = undef; |
- $first_line = undef; |
- } |
- } |
- } |
- else |
- { |
- # Skip other records |
- seek(INPUT, $length, 1); |
- } |
- } |
close(INPUT); |
- |
- if (!scalar(keys(%result))) |
- { |
- die("ERROR: no data found in $bbg_filename!\n"); |
- } |
- return %result; |
-} |
- |
- |
-# |
-# read_hammer_bbg_string(handle, endianness); |
-# |
-# Read a string in 4-byte chunks from HANDLE. |
-# |
-# Return (number of 4-byte chunks read, string). |
-# |
- |
-sub read_hammer_bbg_string(*$) |
-{ |
- my $handle = $_[0]; |
- my $endianness = $_[1]; |
- my $length = 0; |
- my $string = ""; |
- my $packed_word; |
- my $pad; |
- |
- read($handle, $packed_word, 4) == 4 |
- or die("ERROR: reading string\n"); |
- |
- $length = unpack_int32($packed_word, $endianness); |
- $pad = 4 - $length % 4; |
- |
- if ($length == 0) |
- { |
- return (1, undef); |
+ if ($exclude_flag) { |
+ warn("WARNING: unterminated exclusion section in $filename\n"); |
} |
- |
- read($handle, $string, $length) == |
- $length or die("ERROR: reading string\n"); |
- seek($handle, $pad, 1); |
- |
- return(1 + ($length + $pad) / 4, $string); |
-} |
- |
-# |
-# unpack_int32(word, endianness) |
-# |
-# Interpret 4-byte binary string WORD as signed 32 bit integer in |
-# endian encoding defined by ENDIANNESS (0=little, 1=big) and return its |
-# value. |
-# |
- |
-sub unpack_int32($$) |
-{ |
- return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0])); |
+ return(\@result, $branches, \@functions); |
} |
@@ -1831,7 +1865,7 @@ sub get_gcov_version() |
my $version_string; |
my $result; |
- open(GCOV_PIPE, "$gcov_tool -v |") |
+ open(GCOV_PIPE, "-|", "$gcov_tool -v") |
or die("ERROR: cannot retrieve gcov version!\n"); |
$version_string = <GCOV_PIPE>; |
close(GCOV_PIPE); |
@@ -1850,13 +1884,7 @@ sub get_gcov_version() |
$result = $1 << 16 | $2 << 8; |
} |
} |
- if ($version_string =~ /suse/i && $result == 0x30303 || |
- $version_string =~ /mandrake/i && $result == 0x30302) |
- { |
- info("Using compatibility mode for GCC 3.3 (hammer)\n"); |
- $compatibility = $COMPAT_HAMMER; |
- } |
- return $result; |
+ return ($result, $version_string); |
} |
@@ -1919,13 +1947,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 = $?; |
@@ -1934,8 +1963,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; |
} |
@@ -1956,7 +1985,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; |
@@ -1995,8 +2024,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($) |
@@ -2005,51 +2034,175 @@ sub apply_config($) |
foreach (keys(%{$ref})) |
{ |
- if (defined($config->{$_})) |
- { |
+ if (defined($opt_rc{$_})) { |
+ ${$ref->{$_}} = $opt_rc{$_}; |
+ } elsif (defined($config->{$_})) { |
${$ref->{$_}} = $config->{$_}; |
} |
} |
} |
-sub gen_initial_info($) |
+# |
+# get_exclusion_data(filename) |
+# |
+# Scan specified source code file for exclusion markers and return |
+# linenumber -> 1 |
+# for all lines which should be excluded. |
+# |
+ |
+sub get_exclusion_data($) |
{ |
- my $directory = $_[0]; |
- my @file_list; |
+ my ($filename) = @_; |
+ my %list; |
+ my $flag = 0; |
+ local *HANDLE; |
- if (-d $directory) |
- { |
- info("Scanning $directory for $graph_file_extension ". |
- "files ...\n"); |
+ if (!open(HANDLE, "<", $filename)) { |
+ warn("WARNING: could not open $filename\n"); |
+ return undef; |
+ } |
+ while (<HANDLE>) { |
+ if (/$EXCL_STOP/) { |
+ $flag = 0; |
+ } elsif (/$EXCL_START/) { |
+ $flag = 1; |
+ } |
+ if (/$EXCL_LINE/ || $flag) { |
+ $list{$.} = 1; |
+ } |
+ } |
+ close(HANDLE); |
- @file_list = `find "$directory" $maxdepth $follow -name \\*$graph_file_extension -type f 2>/dev/null`; |
- chomp(@file_list); |
- @file_list or die("ERROR: no $graph_file_extension files ". |
- "found in $directory!\n"); |
- info("Found %d graph files in %s\n", $#file_list+1, $directory); |
+ if ($flag) { |
+ warn("WARNING: unterminated exclusion section in $filename\n"); |
} |
- else |
- { |
- @file_list = ($directory); |
+ |
+ return \%list; |
+} |
+ |
+ |
+# |
+# apply_exclusion_data(instr, graph) |
+# |
+# Remove lines from instr and graph data structures which are marked |
+# for exclusion in the source code file. |
+# |
+# Return adjusted (instr, graph). |
+# |
+# graph : file name -> function data |
+# function data : function name -> line data |
+# line data : [ line1, line2, ... ] |
+# |
+# instr : filename -> line data |
+# line data : [ line1, line2, ... ] |
+# |
+ |
+sub apply_exclusion_data($$) |
+{ |
+ my ($instr, $graph) = @_; |
+ my $filename; |
+ my %excl_data; |
+ my $excl_read_failed = 0; |
+ |
+ # Collect exclusion marker data |
+ foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) { |
+ my $excl = get_exclusion_data($filename); |
+ |
+ # Skip and note if file could not be read |
+ if (!defined($excl)) { |
+ $excl_read_failed = 1; |
+ next; |
+ } |
+ |
+ # Add to collection if there are markers |
+ $excl_data{$filename} = $excl if (keys(%{$excl}) > 0); |
} |
- # Process all files in list |
- foreach (@file_list) { process_graphfile($_); } |
+ # Warn if not all source files could be read |
+ if ($excl_read_failed) { |
+ warn("WARNING: some exclusion markers may be ignored\n"); |
+ } |
+ |
+ # Skip if no markers were found |
+ return ($instr, $graph) if (keys(%excl_data) == 0); |
+ |
+ # Apply exclusion marker data to graph |
+ foreach $filename (keys(%excl_data)) { |
+ my $function_data = $graph->{$filename}; |
+ my $excl = $excl_data{$filename}; |
+ my $function; |
+ |
+ next if (!defined($function_data)); |
+ |
+ foreach $function (keys(%{$function_data})) { |
+ my $line_data = $function_data->{$function}; |
+ my $line; |
+ my @new_data; |
+ |
+ # To be consistent with exclusion parser in non-initial |
+ # case we need to remove a function if the first line |
+ # was excluded |
+ if ($excl->{$line_data->[0]}) { |
+ delete($function_data->{$function}); |
+ next; |
+ } |
+ # Copy only lines which are not excluded |
+ foreach $line (@{$line_data}) { |
+ push(@new_data, $line) if (!$excl->{$line}); |
+ } |
+ |
+ # Store modified list |
+ if (scalar(@new_data) > 0) { |
+ $function_data->{$function} = \@new_data; |
+ } else { |
+ # All of this function was excluded |
+ delete($function_data->{$function}); |
+ } |
+ } |
+ |
+ # Check if all functions of this file were excluded |
+ if (keys(%{$function_data}) == 0) { |
+ delete($graph->{$filename}); |
+ } |
+ } |
+ |
+ # Apply exclusion marker data to instr |
+ foreach $filename (keys(%excl_data)) { |
+ my $line_data = $instr->{$filename}; |
+ my $excl = $excl_data{$filename}; |
+ my $line; |
+ my @new_data; |
+ |
+ next if (!defined($line_data)); |
+ |
+ # Copy only lines which are not excluded |
+ foreach $line (@{$line_data}) { |
+ push(@new_data, $line) if (!$excl->{$line}); |
+ } |
+ |
+ # Store modified list |
+ $instr->{$filename} = \@new_data; |
+ } |
+ |
+ return ($instr, $graph); |
} |
-sub process_graphfile($) |
+ |
+sub process_graphfile($$) |
{ |
- my $graph_filename = $_[0]; |
+ my ($file, $dir) = @_; |
+ my $graph_filename = $file; |
my $graph_dir; |
my $graph_basename; |
my $source_dir; |
my $base_dir; |
- my %graph_data; |
+ my $graph; |
+ my $instr; |
my $filename; |
local *INFO_HANDLE; |
- info("Processing $_[0]\n"); |
+ info("Processing %s\n", abs2rel($file, $dir)); |
# Get path to data file in absolute and normalized form (begins with /, |
# contains no more ../ or ./) |
@@ -2058,11 +2211,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 |
@@ -2077,19 +2229,30 @@ sub process_graphfile($) |
if ($gcov_version < $GCOV_VERSION_3_4_0) |
{ |
- if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) |
+ if (is_compat($COMPAT_MODE_HAMMER)) |
{ |
- %graph_data = read_hammer_bbg_file($graph_filename, |
- $base_dir); |
+ ($instr, $graph) = read_bbg($graph_filename); |
} |
else |
{ |
- %graph_data = read_bb_file($graph_filename, $base_dir); |
+ ($instr, $graph) = read_bb($graph_filename); |
} |
} |
else |
{ |
- %graph_data = read_gcno_file($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); |
} |
# Check whether we're writing to a single file |
@@ -2102,7 +2265,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"); |
} |
@@ -2110,51 +2273,51 @@ 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"); |
} |
# Write test name |
printf(INFO_HANDLE "TN:%s\n", $test_name); |
- foreach $filename (keys(%graph_data)) |
+ foreach $filename (sort(keys(%{$instr}))) |
{ |
- my %lines; |
- my $count = 0; |
- my @functions; |
+ my $funcdata = $graph->{$filename}; |
+ my $line; |
+ my $linedata; |
print(INFO_HANDLE "SF:$filename\n"); |
- # Write function related data |
- foreach (split(",",$graph_data{$filename})) |
- { |
- my ($fn, $line) = split("=", $_); |
- |
- if ($fn eq "") |
- { |
- $lines{$line} = ""; |
- next; |
- } |
+ if (defined($funcdata) && $func_coverage) { |
+ my @functions = sort {$funcdata->{$a}->[0] <=> |
+ $funcdata->{$b}->[0]} |
+ keys(%{$funcdata}); |
+ my $func; |
- # Normalize function name |
- $fn =~ s/\W/_/g; |
+ # Gather list of instrumented lines and functions |
+ foreach $func (@functions) { |
+ $linedata = $funcdata->{$func}; |
- print(INFO_HANDLE "FN:$line,$fn\n"); |
- push(@functions, $fn); |
- } |
- foreach (@functions) { |
- print(INFO_HANDLE "FNDA:$_,0\n"); |
+ # Print function name and starting line |
+ print(INFO_HANDLE "FN:".$linedata->[0]. |
+ ",".filter_fn_name($func)."\n"); |
+ } |
+ # Print zero function coverage data |
+ foreach $func (@functions) { |
+ print(INFO_HANDLE "FNDA:0,". |
+ filter_fn_name($func)."\n"); |
+ } |
+ # Print function summary |
+ print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); |
+ print(INFO_HANDLE "FNH:0\n"); |
} |
- print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); |
- print(INFO_HANDLE "FNH:0\n"); |
- |
- # Write line related data |
- foreach (sort {$a <=> $b } keys(%lines)) |
- { |
- print(INFO_HANDLE "DA:$_,0\n"); |
- $count++; |
+ # Print zero line coverage data |
+ foreach $line (@{$instr->{$filename}}) { |
+ print(INFO_HANDLE "DA:$line,0\n"); |
} |
+ # Print line summary |
+ print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n"); |
print(INFO_HANDLE "LH:0\n"); |
- print(INFO_HANDLE "LF:$count\n"); |
+ |
print(INFO_HANDLE "end_of_record\n"); |
} |
if (!($output_filename && ($output_filename eq "-"))) |
@@ -2163,6 +2326,16 @@ sub process_graphfile($) |
} |
} |
+sub filter_fn_name($) |
+{ |
+ my ($fn) = @_; |
+ |
+ # Remove characters used internally as function name delimiters |
+ $fn =~ s/[,=]/_/g; |
+ |
+ return $fn; |
+} |
+ |
sub warn_handler($) |
{ |
my ($msg) = @_; |
@@ -2176,3 +2349,1309 @@ sub die_handler($) |
die("$tool_name: $msg"); |
} |
+ |
+ |
+# |
+# graph_error(filename, message) |
+# |
+# Print message about error in graph file. If ignore_graph_error is set, return. |
+# Otherwise abort. |
+# |
+ |
+sub graph_error($$) |
+{ |
+ my ($filename, $msg) = @_; |
+ |
+ if ($ignore[$ERROR_GRAPH]) { |
+ warn("WARNING: $filename: $msg - skipping\n"); |
+ return; |
+ } |
+ die("ERROR: $filename: $msg\n"); |
+} |
+ |
+# |
+# graph_expect(description) |
+# |
+# If debug is set to a non-zero value, print the specified description of what |
+# is expected to be read next from the graph file. |
+# |
+ |
+sub graph_expect($) |
+{ |
+ my ($msg) = @_; |
+ |
+ if (!$debug || !defined($msg)) { |
+ return; |
+ } |
+ |
+ print(STDERR "DEBUG: expecting $msg\n"); |
+} |
+ |
+# |
+# 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 PEEK is non-zero, reset |
+# file position after read. |
+# |
+ |
+sub graph_read(*$;$$) |
+{ |
+ 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: $op($length)=$result: "); |
+ for ($i = 0; $i < length($data); $i++) { |
+ my $c = substr($data, $i, 1);; |
+ my $n = ord($c); |
+ |
+ $hex .= sprintf("%02x ", $n); |
+ if ($n >= 32 && $n <= 127) { |
+ $ascii .= $c; |
+ } else { |
+ $ascii .= "."; |
+ } |
+ } |
+ 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; |
+ } |
+ return $data; |
+} |
+ |
+# |
+# graph_skip(handle, bytes[, description]) |
+# |
+# Read and discard the specified number of bytes from handle. Return non-zero |
+# if bytes could be read, zero otherwise. |
+# |
+ |
+sub graph_skip(*$;$) |
+{ |
+ my ($handle, $length, $desc) = @_; |
+ |
+ if (defined(graph_read($handle, $length, $desc))) { |
+ return 1; |
+ } |
+ return 0; |
+} |
+ |
+# |
+# sort_uniq(list) |
+# |
+# Return list in numerically ascending order and without duplicate entries. |
+# |
+ |
+sub sort_uniq(@) |
+{ |
+ my (@list) = @_; |
+ my %hash; |
+ |
+ foreach (@list) { |
+ $hash{$_} = 1; |
+ } |
+ return sort { $a <=> $b } keys(%hash); |
+} |
+ |
+# |
+# sort_uniq_lex(list) |
+# |
+# Return list in lexically ascending order and without duplicate entries. |
+# |
+ |
+sub sort_uniq_lex(@) |
+{ |
+ my (@list) = @_; |
+ my %hash; |
+ |
+ foreach (@list) { |
+ $hash{$_} = 1; |
+ } |
+ return sort keys(%hash); |
+} |
+ |
+# |
+# 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. |
+# Sort list of line numbers numerically ascending. |
+# |
+ |
+sub graph_cleanup($) |
+{ |
+ my ($graph) = @_; |
+ my $filename; |
+ |
+ foreach $filename (keys(%{$graph})) { |
+ my $per_file = $graph->{$filename}; |
+ my $function; |
+ |
+ foreach $function (keys(%{$per_file})) { |
+ my $lines = $per_file->{$function}; |
+ |
+ if (scalar(@$lines) == 0) { |
+ # Remove empty function |
+ delete($per_file->{$function}); |
+ next; |
+ } |
+ # Normalize list |
+ $per_file->{$function} = [ sort_uniq(@$lines) ]; |
+ } |
+ if (scalar(keys(%{$per_file})) == 0) { |
+ # Remove empty file |
+ delete($graph->{$filename}); |
+ } |
+ } |
+} |
+ |
+# |
+# graph_find_base(bb) |
+# |
+# Try to identify the filename which is the base source file for the |
+# specified bb data. |
+# |
+ |
+sub graph_find_base($) |
+{ |
+ my ($bb) = @_; |
+ my %file_count; |
+ my $basefile; |
+ my $file; |
+ my $func; |
+ my $filedata; |
+ my $count; |
+ my $num; |
+ |
+ # Identify base name for this bb data. |
+ foreach $func (keys(%{$bb})) { |
+ $filedata = $bb->{$func}; |
+ |
+ foreach $file (keys(%{$filedata})) { |
+ $count = $file_count{$file}; |
+ |
+ # Count file occurrence |
+ $file_count{$file} = defined($count) ? $count + 1 : 1; |
+ } |
+ } |
+ $count = 0; |
+ $num = 0; |
+ foreach $file (keys(%file_count)) { |
+ if ($file_count{$file} > $count) { |
+ # The file that contains code for the most functions |
+ # is likely the base file |
+ $count = $file_count{$file}; |
+ $num = 1; |
+ $basefile = $file; |
+ } elsif ($file_count{$file} == $count) { |
+ # If more than one file could be the basefile, we |
+ # don't have a basefile |
+ $basefile = undef; |
+ } |
+ } |
+ |
+ return $basefile; |
+} |
+ |
+# |
+# graph_from_bb(bb, fileorder, bb_filename) |
+# |
+# Convert data from bb to the graph format and list of instrumented lines. |
+# Returns (instr, graph). |
+# |
+# bb : function name -> file data |
+# : undef -> file order |
+# file data : filename -> line data |
+# line data : [ line1, line2, ... ] |
+# |
+# file order : function name -> [ filename1, filename2, ... ] |
+# |
+# graph : file name -> function data |
+# function data : function name -> line data |
+# line data : [ line1, line2, ... ] |
+# |
+# instr : filename -> line data |
+# line data : [ line1, line2, ... ] |
+# |
+ |
+sub graph_from_bb($$$) |
+{ |
+ my ($bb, $fileorder, $bb_filename) = @_; |
+ my $graph = {}; |
+ my $instr = {}; |
+ my $basefile; |
+ my $file; |
+ my $func; |
+ my $filedata; |
+ my $linedata; |
+ my $order; |
+ |
+ $basefile = graph_find_base($bb); |
+ # Create graph structure |
+ foreach $func (keys(%{$bb})) { |
+ $filedata = $bb->{$func}; |
+ $order = $fileorder->{$func}; |
+ |
+ # Account for lines in functions |
+ if (defined($basefile) && defined($filedata->{$basefile})) { |
+ # If the basefile contributes to this function, |
+ # account this function to the basefile. |
+ $graph->{$basefile}->{$func} = $filedata->{$basefile}; |
+ } else { |
+ # If the basefile does not contribute to this function, |
+ # account this function to the first file contributing |
+ # lines. |
+ $graph->{$order->[0]}->{$func} = |
+ $filedata->{$order->[0]}; |
+ } |
+ |
+ foreach $file (keys(%{$filedata})) { |
+ # Account for instrumented lines |
+ $linedata = $filedata->{$file}; |
+ push(@{$instr->{$file}}, @$linedata); |
+ } |
+ } |
+ # Clean up array of instrumented lines |
+ foreach $file (keys(%{$instr})) { |
+ $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ]; |
+ } |
+ |
+ return ($instr, $graph); |
+} |
+ |
+# |
+# graph_add_order(fileorder, function, filename) |
+# |
+# Add an entry for filename to the fileorder data set for function. |
+# |
+ |
+sub graph_add_order($$$) |
+{ |
+ my ($fileorder, $function, $filename) = @_; |
+ my $item; |
+ my $list; |
+ |
+ $list = $fileorder->{$function}; |
+ foreach $item (@$list) { |
+ if ($item eq $filename) { |
+ return; |
+ } |
+ } |
+ push(@$list, $filename); |
+ $fileorder->{$function} = $list; |
+} |
+ |
+# |
+# read_bb_word(handle[, description]) |
+# |
+# Read and return a word in .bb format from handle. |
+# |
+ |
+sub read_bb_word(*;$) |
+{ |
+ my ($handle, $desc) = @_; |
+ |
+ return graph_read($handle, 4, $desc); |
+} |
+ |
+# |
+# read_bb_value(handle[, description]) |
+# |
+# Read a word in .bb format from handle and return the word and its integer |
+# value. |
+# |
+ |
+sub read_bb_value(*;$) |
+{ |
+ my ($handle, $desc) = @_; |
+ my $word; |
+ |
+ $word = read_bb_word($handle, $desc); |
+ return undef if (!defined($word)); |
+ |
+ return ($word, unpack("V", $word)); |
+} |
+ |
+# |
+# read_bb_string(handle, delimiter) |
+# |
+# Read and return a string in .bb format from handle up to the specified |
+# delimiter value. |
+# |
+ |
+sub read_bb_string(*$) |
+{ |
+ my ($handle, $delimiter) = @_; |
+ my $word; |
+ my $value; |
+ my $string = ""; |
+ |
+ graph_expect("string"); |
+ do { |
+ ($word, $value) = read_bb_value($handle, "string or delimiter"); |
+ return undef if (!defined($value)); |
+ if ($value != $delimiter) { |
+ $string .= $word; |
+ } |
+ } while ($value != $delimiter); |
+ $string =~ s/\0//g; |
+ |
+ return $string; |
+} |
+ |
+# |
+# read_bb(filename) |
+# |
+# Read the contents of the specified .bb file and return (instr, graph), where: |
+# |
+# instr : filename -> line data |
+# line data : [ line1, line2, ... ] |
+# |
+# graph : filename -> file_data |
+# file_data : function name -> line_data |
+# line_data : [ line1, line2, ... ] |
+# |
+# See the gcov info pages of gcc 2.95 for a description of the .bb file format. |
+# |
+ |
+sub read_bb($) |
+{ |
+ my ($bb_filename) = @_; |
+ my $minus_one = 0x80000001; |
+ my $minus_two = 0x80000002; |
+ my $value; |
+ my $filename; |
+ my $function; |
+ my $bb = {}; |
+ my $fileorder = {}; |
+ my $instr; |
+ my $graph; |
+ local *HANDLE; |
+ |
+ open(HANDLE, "<", $bb_filename) or goto open_error; |
+ binmode(HANDLE); |
+ while (!eof(HANDLE)) { |
+ $value = read_bb_value(*HANDLE, "data word"); |
+ goto incomplete if (!defined($value)); |
+ if ($value == $minus_one) { |
+ # Source file name |
+ graph_expect("filename"); |
+ $filename = read_bb_string(*HANDLE, $minus_one); |
+ goto incomplete if (!defined($filename)); |
+ } elsif ($value == $minus_two) { |
+ # Function name |
+ graph_expect("function name"); |
+ $function = read_bb_string(*HANDLE, $minus_two); |
+ goto incomplete if (!defined($function)); |
+ } elsif ($value > 0) { |
+ # Line number |
+ if (!defined($filename) || !defined($function)) { |
+ warn("WARNING: unassigned line number ". |
+ "$value\n"); |
+ next; |
+ } |
+ push(@{$bb->{$function}->{$filename}}, $value); |
+ graph_add_order($fileorder, $function, $filename); |
+ } |
+ } |
+ close(HANDLE); |
+ ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename); |
+ graph_cleanup($graph); |
+ |
+ return ($instr, $graph); |
+ |
+open_error: |
+ graph_error($bb_filename, "could not open file"); |
+ return undef; |
+incomplete: |
+ graph_error($bb_filename, "reached unexpected end of file"); |
+ return undef; |
+} |
+ |
+# |
+# read_bbg_word(handle[, description]) |
+# |
+# Read and return a word in .bbg format. |
+# |
+ |
+sub read_bbg_word(*;$) |
+{ |
+ my ($handle, $desc) = @_; |
+ |
+ return graph_read($handle, 4, $desc); |
+} |
+ |
+# |
+# read_bbg_value(handle[, description]) |
+# |
+# Read a word in .bbg format from handle and return its integer value. |
+# |
+ |
+sub read_bbg_value(*;$) |
+{ |
+ my ($handle, $desc) = @_; |
+ my $word; |
+ |
+ $word = read_bbg_word($handle, $desc); |
+ return undef if (!defined($word)); |
+ |
+ return unpack("N", $word); |
+} |
+ |
+# |
+# read_bbg_string(handle) |
+# |
+# Read and return a string in .bbg format. |
+# |
+ |
+sub read_bbg_string(*) |
+{ |
+ my ($handle, $desc) = @_; |
+ my $length; |
+ my $string; |
+ |
+ graph_expect("string"); |
+ # Read string length |
+ $length = read_bbg_value($handle, "string length"); |
+ return undef if (!defined($length)); |
+ if ($length == 0) { |
+ return ""; |
+ } |
+ # Read string |
+ $string = graph_read($handle, $length, "string"); |
+ return undef if (!defined($string)); |
+ # Skip padding |
+ graph_skip($handle, 4 - $length % 4, "string padding") or return undef; |
+ |
+ return $string; |
+} |
+ |
+# |
+# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, |
+# 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(*$$$$$) |
+{ |
+ my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_; |
+ my $string; |
+ my $lineno; |
+ |
+ graph_expect("lines record"); |
+ # Skip basic block index |
+ graph_skip($handle, 4, "basic block index") or return undef; |
+ while (1) { |
+ # Read line number |
+ $lineno = read_bbg_value($handle, "line number"); |
+ return undef if (!defined($lineno)); |
+ if ($lineno == 0) { |
+ # Got a marker for a new filename |
+ graph_expect("filename"); |
+ $string = read_bbg_string($handle); |
+ return undef if (!defined($string)); |
+ # Check for end of record |
+ if ($string eq "") { |
+ return $filename; |
+ } |
+ $filename = $string; |
+ if (!exists($bb->{$function}->{$filename})) { |
+ $bb->{$function}->{$filename} = []; |
+ } |
+ next; |
+ } |
+ # Got an actual line number |
+ if (!defined($filename)) { |
+ warn("WARNING: unassigned line number in ". |
+ "$bbg_filename\n"); |
+ next; |
+ } |
+ push(@{$bb->{$function}->{$filename}}, $lineno); |
+ graph_add_order($fileorder, $function, $filename); |
+ } |
+} |
+ |
+# |
+# 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, ... ] |
+# |
+# 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($) |
+{ |
+ my ($bbg_filename) = @_; |
+ my $file_magic = 0x67626267; |
+ my $tag_function = 0x01000000; |
+ my $tag_lines = 0x01450000; |
+ my $word; |
+ my $tag; |
+ my $length; |
+ my $function; |
+ my $filename; |
+ my $bb = {}; |
+ my $fileorder = {}; |
+ my $instr; |
+ my $graph; |
+ local *HANDLE; |
+ |
+ open(HANDLE, "<", $bbg_filename) or goto open_error; |
+ binmode(HANDLE); |
+ # Read magic |
+ $word = read_bbg_value(*HANDLE, "file magic"); |
+ goto incomplete if (!defined($word)); |
+ # Check magic |
+ if ($word != $file_magic) { |
+ goto magic_error; |
+ } |
+ # Skip version |
+ graph_skip(*HANDLE, 4, "version") or goto incomplete; |
+ while (!eof(HANDLE)) { |
+ # Read record tag |
+ $tag = read_bbg_value(*HANDLE, "record tag"); |
+ goto incomplete if (!defined($tag)); |
+ # Read record length |
+ $length = read_bbg_value(*HANDLE, "record length"); |
+ goto incomplete if (!defined($tag)); |
+ if ($tag == $tag_function) { |
+ graph_expect("function record"); |
+ # Read function name |
+ graph_expect("function name"); |
+ $function = read_bbg_string(*HANDLE); |
+ goto incomplete if (!defined($function)); |
+ $filename = undef; |
+ # Skip function checksum |
+ graph_skip(*HANDLE, 4, "function checksum") |
+ or goto incomplete; |
+ } elsif ($tag == $tag_lines) { |
+ # Read lines record |
+ $filename = read_bbg_lines_record(HANDLE, $bbg_filename, |
+ $bb, $fileorder, $filename, |
+ $function); |
+ goto incomplete if (!defined($filename)); |
+ } else { |
+ # Skip record contents |
+ graph_skip(*HANDLE, $length, "unhandled record") |
+ or goto incomplete; |
+ } |
+ } |
+ close(HANDLE); |
+ ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename); |
+ graph_cleanup($graph); |
+ |
+ return ($instr, $graph); |
+ |
+open_error: |
+ graph_error($bbg_filename, "could not open file"); |
+ return undef; |
+incomplete: |
+ graph_error($bbg_filename, "reached unexpected end of file"); |
+ return undef; |
+magic_error: |
+ graph_error($bbg_filename, "found unrecognized bbg file magic"); |
+ return undef; |
+} |
+ |
+# |
+# read_gcno_word(handle[, description, peek]) |
+# |
+# Read and return a word in .gcno format. |
+# |
+ |
+sub read_gcno_word(*;$$) |
+{ |
+ my ($handle, $desc, $peek) = @_; |
+ |
+ return graph_read($handle, 4, $desc, $peek); |
+} |
+ |
+# |
+# 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. If PEEK is non-zero, reset file |
+# position after read. |
+# |
+ |
+sub read_gcno_value(*$;$$) |
+{ |
+ my ($handle, $big_endian, $desc, $peek) = @_; |
+ my $word; |
+ my $pos; |
+ |
+ $word = read_gcno_word($handle, $desc, $peek); |
+ return undef if (!defined($word)); |
+ if ($big_endian) { |
+ return unpack("N", $word); |
+ } else { |
+ return unpack("V", $word); |
+ } |
+} |
+ |
+# |
+# read_gcno_string(handle, big_endian) |
+# |
+# Read and return a string in .gcno format. |
+# |
+ |
+sub read_gcno_string(*$) |
+{ |
+ my ($handle, $big_endian) = @_; |
+ my $length; |
+ my $string; |
+ |
+ graph_expect("string"); |
+ # Read string length |
+ $length = read_gcno_value($handle, $big_endian, "string length"); |
+ return undef if (!defined($length)); |
+ if ($length == 0) { |
+ return ""; |
+ } |
+ $length *= 4; |
+ # Read string |
+ $string = graph_read($handle, $length, "string and padding"); |
+ return undef if (!defined($string)); |
+ $string =~ s/\0//g; |
+ |
+ return $string; |
+} |
+ |
+# |
+# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, |
+# 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(*$$$$$$) |
+{ |
+ my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, |
+ $big_endian) = @_; |
+ my $string; |
+ my $lineno; |
+ |
+ graph_expect("lines record"); |
+ # Skip basic block index |
+ graph_skip($handle, 4, "basic block index") or return undef; |
+ while (1) { |
+ # Read line number |
+ $lineno = read_gcno_value($handle, $big_endian, "line number"); |
+ return undef if (!defined($lineno)); |
+ if ($lineno == 0) { |
+ # Got a marker for a new filename |
+ graph_expect("filename"); |
+ $string = read_gcno_string($handle, $big_endian); |
+ return undef if (!defined($string)); |
+ # Check for end of record |
+ if ($string eq "") { |
+ return $filename; |
+ } |
+ $filename = $string; |
+ if (!exists($bb->{$function}->{$filename})) { |
+ $bb->{$function}->{$filename} = []; |
+ } |
+ next; |
+ } |
+ # Got an actual line number |
+ if (!defined($filename)) { |
+ warn("WARNING: unassigned line number in ". |
+ "$gcno_filename\n"); |
+ next; |
+ } |
+ # Add to list |
+ push(@{$bb->{$function}->{$filename}}, $lineno); |
+ graph_add_order($fileorder, $function, $filename); |
+ } |
+} |
+ |
+# |
+# 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. |
+# |
+ |
+sub read_gcno_function_record(*$$$$) |
+{ |
+ my ($handle, $bb, $fileorder, $big_endian, $rec_length) = @_; |
+ my $filename; |
+ my $function; |
+ my $lineno; |
+ my $lines; |
+ |
+ 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); |
+ return undef if (!defined($function)); |
+ # Read filename |
+ graph_expect("filename"); |
+ $filename = read_gcno_string($handle, $big_endian); |
+ return undef if (!defined($filename)); |
+ # Read first line number |
+ $lineno = read_gcno_value($handle, $big_endian, "initial line number"); |
+ return undef if (!defined($lineno)); |
+ # Add to list |
+ push(@{$bb->{$function}->{$filename}}, $lineno); |
+ graph_add_order($fileorder, $function, $filename); |
+ |
+ return ($filename, $function); |
+} |
+ |
+# |
+# read_gcno(filename) |
+# |
+# Read the contents of the specified .gcno file and return the following |
+# mapping: |
+# graph: filename -> file_data |
+# file_data: function name -> line_data |
+# line_data: [ line1, line2, ... ] |
+# |
+# See the gcov-io.h file in the gcc 3.3 source code for a description of |
+# the .gcno format. |
+# |
+ |
+sub read_gcno($) |
+{ |
+ my ($gcno_filename) = @_; |
+ my $file_magic = 0x67636e6f; |
+ my $tag_function = 0x01000000; |
+ my $tag_lines = 0x01450000; |
+ my $big_endian; |
+ my $word; |
+ my $tag; |
+ my $length; |
+ my $filename; |
+ my $function; |
+ my $bb = {}; |
+ my $fileorder = {}; |
+ my $instr; |
+ my $graph; |
+ local *HANDLE; |
+ |
+ open(HANDLE, "<", $gcno_filename) or goto open_error; |
+ binmode(HANDLE); |
+ # Read magic |
+ $word = read_gcno_word(*HANDLE, "file magic"); |
+ goto incomplete if (!defined($word)); |
+ # Determine file endianness |
+ if (unpack("N", $word) == $file_magic) { |
+ $big_endian = 1; |
+ } elsif (unpack("V", $word) == $file_magic) { |
+ $big_endian = 0; |
+ } else { |
+ goto magic_error; |
+ } |
+ # Skip version and stamp |
+ graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete; |
+ while (!eof(HANDLE)) { |
+ my $next_pos; |
+ my $curr_pos; |
+ |
+ # Read record tag |
+ $tag = read_gcno_value(*HANDLE, $big_endian, "record tag"); |
+ goto incomplete if (!defined($tag)); |
+ # Read record length |
+ $length = read_gcno_value(*HANDLE, $big_endian, |
+ "record length"); |
+ goto incomplete if (!defined($length)); |
+ # Convert length to bytes |
+ $length *= 4; |
+ # Calculate start of next record |
+ $next_pos = tell(HANDLE); |
+ goto tell_error if ($next_pos == -1); |
+ $next_pos += $length; |
+ # Process record |
+ if ($tag == $tag_function) { |
+ ($filename, $function) = read_gcno_function_record( |
+ *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, |
+ $big_endian); |
+ goto incomplete if (!defined($filename)); |
+ } else { |
+ # Skip record contents |
+ graph_skip(*HANDLE, $length, "unhandled record") |
+ or goto incomplete; |
+ } |
+ # Ensure that we are at the start of the next record |
+ $curr_pos = tell(HANDLE); |
+ goto tell_error if ($curr_pos == -1); |
+ next if ($curr_pos == $next_pos); |
+ goto record_error if ($curr_pos > $next_pos); |
+ graph_skip(*HANDLE, $next_pos - $curr_pos, |
+ "unhandled record content") |
+ or goto incomplete; |
+ } |
+ close(HANDLE); |
+ ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename); |
+ graph_cleanup($graph); |
+ |
+ return ($instr, $graph); |
+ |
+open_error: |
+ graph_error($gcno_filename, "could not open file"); |
+ return undef; |
+incomplete: |
+ graph_error($gcno_filename, "reached unexpected end of file"); |
+ return undef; |
+magic_error: |
+ graph_error($gcno_filename, "found unrecognized gcno file magic"); |
+ return undef; |
+tell_error: |
+ graph_error($gcno_filename, "could not determine file position"); |
+ return undef; |
+record_error: |
+ graph_error($gcno_filename, "found unrecognized record format"); |
+ return undef; |
+} |
+ |
+sub debug($) |
+{ |
+ my ($msg) = @_; |
+ |
+ return if (!$debug); |
+ print(STDERR "DEBUG: $msg"); |
+} |
+ |
+# |
+# get_gcov_capabilities |
+# |
+# Determine the list of available gcov options. |
+# |
+ |
+sub get_gcov_capabilities() |
+{ |
+ my $help = `$gcov_tool --help`; |
+ my %capabilities; |
+ |
+ foreach (split(/\n/, $help)) { |
+ next if (!/--(\S+)/); |
+ next if ($1 eq 'help'); |
+ next if ($1 eq 'version'); |
+ next if ($1 eq 'object-directory'); |
+ |
+ $capabilities{$1} = 1; |
+ debug("gcov has capability '$1'\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; |
+} |