| 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;
|
| +}
|
|
|