diff options
author | Remko Tronçon <git@el-tramo.be> | 2009-06-01 12:13:57 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2009-06-01 12:13:57 (GMT) |
commit | e16c66185e9f633a05911d708e6404d9f741c7b1 (patch) | |
tree | 0fbf13ca770c61ddb4d9976be7ab549cf881ba70 /3rdParty/LCov/geninfo | |
parent | c1885ea7854df8cf79d04db903b4352934190749 (diff) | |
download | swift-contrib-e16c66185e9f633a05911d708e6404d9f741c7b1.zip swift-contrib-e16c66185e9f633a05911d708e6404d9f741c7b1.tar.bz2 |
Added LCov.
Diffstat (limited to '3rdParty/LCov/geninfo')
-rwxr-xr-x | 3rdParty/LCov/geninfo | 2178 |
1 files changed, 2178 insertions, 0 deletions
diff --git a/3rdParty/LCov/geninfo b/3rdParty/LCov/geninfo new file mode 100755 index 0000000..055641b --- /dev/null +++ b/3rdParty/LCov/geninfo @@ -0,0 +1,2178 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002,2007 +# +# 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 +# the Free Software Foundation; either version 2 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# geninfo +# +# This script generates .info files from data files as created by code +# instrumented with gcc's built-in profiling mechanism. Call it with +# --help and refer to the geninfo man page to get information on usage +# and available options. +# +# +# Authors: +# 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> +# IBM Lab Boeblingen +# based on code by Manoj Iyer <manjo@mail.utexas.edu> and +# Megan Bock <mbock@us.ibm.com> +# IBM Austin +# 2002-09-05 / Peter Oberparleiter: implemented option that allows file list +# 2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also +# parse the new gcov format which is to be introduced in gcc 3.3 +# 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT +# 2003-07-03 / Peter Oberparleiter: added line checksum support, added +# --no-checksum +# 2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV +# 2003-12-11 / Laurent Deniel: added --follow option +# workaround gcov (<= 3.2.x) bug with empty .da files +# 2004-01-03 / Laurent Deniel: Ignore empty .bb files +# 2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and +# gcov versioning +# 2004-08-09 / Peter Oberparleiter: added configuration file support +# 2008-07-14 / Tom Zoerner: added --function-coverage command line option +# 2008-08-13 / Peter Oberparleiter: modified function coverage +# implementation (now enabled per default) +# + +use strict; +use File::Basename; +use Getopt::Long; +use Digest::MD5 qw(md5_base64); + + +# Constants +our $lcov_version = "LCOV version 1.7"; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $gcov_tool = "gcov"; +our $tool_name = basename($0); + +our $GCOV_VERSION_3_4_0 = 0x30400; +our $GCOV_VERSION_3_3_0 = 0x30300; +our $GCNO_FUNCTION_TAG = 0x01000000; +our $GCNO_LINES_TAG = 0x01450000; +our $GCNO_FILE_MAGIC = 0x67636e6f; +our $BBG_FILE_MAGIC = 0x67626267; + +our $COMPAT_HAMMER = "hammer"; + +our $ERROR_GCOV = 0; +our $ERROR_SOURCE = 1; + +# Prototypes +sub print_usage(*); +sub gen_info($); +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 warn_handler($); +sub die_handler($); + +# Global variables +our $gcov_version; +our $graph_file_extension; +our $data_file_extension; +our @data_directory; +our $test_name = ""; +our $quiet; +our $help; +our $output_filename; +our $base_directory; +our $version; +our $follow; +our $checksum; +our $no_checksum; +our $preserve_paths; +our $compat_libtool; +our $no_compat_libtool; +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 $cwd = `pwd`; +chomp($cwd); + + +# +# Code entry point +# + +# Register handler routine to be called when interrupted +$SIG{"INT"} = \&int_handler; +$SIG{__WARN__} = \&warn_handler; +$SIG{__DIE__} = \&die_handler; + +# Read configuration file if available +if (-r $ENV{"HOME"}."/.lcovrc") +{ + $config = read_config($ENV{"HOME"}."/.lcovrc"); +} +elsif (-r "/etc/lcovrc") +{ + $config = read_config("/etc/lcovrc"); +} + +if ($config) +{ + # Copy configuration file 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}); + + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } +} + +# Parse command line options +if (!GetOptions("test-name=s" => \$test_name, + "output-filename=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, + "gcov-tool=s" => \$gcov_tool, + "ignore-errors=s" => \@ignore_errors, + "initial|i" => \$initial, + "no-recursion" => \$no_recursion, + )) +{ + print(STDERR "Use $tool_name --help to get usage information\n"); + exit(1); +} +else +{ + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } + + if (defined($no_compat_libtool)) + { + $compat_libtool = ($no_compat_libtool ? 0 : 1); + $no_compat_libtool = undef; + } +} + +@data_directory = @ARGV; + +# Check for help option +if ($help) +{ + print_usage(*STDOUT); + exit(0); +} + +# Check for version option +if ($version) +{ + print("$tool_name: $lcov_version\n"); + exit(0); +} + +# Make sure test names only contain valid characters +if ($test_name =~ s/\W/_/g) +{ + warn("WARNING: invalid characters removed from testname!\n"); +} + +# Adjust test name to include uname output if requested +if ($adjust_testname) +{ + $test_name .= "__".`uname -a`; + $test_name =~ s/\W/_/g; +} + +# Make sure base_directory contains an absolute path specification +if ($base_directory) +{ + $base_directory = solve_relative_path($cwd, $base_directory); +} + +# Check for follow option +if ($follow) +{ + $follow = "-follow" +} +else +{ + $follow = ""; +} + +# Determine checksum mode +if (defined($checksum)) +{ + # Normalize to boolean + $checksum = ($checksum ? 1 : 0); +} +else +{ + # Default is off + $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) +{ + $maxdepth = "-maxdepth 1"; +} +else +{ + $maxdepth = ""; +} + +# Check for directory name +if (!@data_directory) +{ + die("No directory specified\n". + "Use $tool_name --help to get usage information\n"); +} +else +{ + foreach (@data_directory) + { + stat($_); + if (!-r _) + { + die("ERROR: cannot read $_!\n"); + } + } +} + +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) + { + $data_file_extension = ".da"; + $graph_file_extension = ".bbg"; + } + else + { + $data_file_extension = ".da"; + $graph_file_extension = ".bb"; + } +} +else +{ + $data_file_extension = ".gcda"; + $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") + or die("ERROR: cannot create $output_filename!\n"); + close(DUMMY_HANDLE); + + # Make $output_filename an absolute path because we're going + # to change directories while processing files + if (!($output_filename =~ /^\/(.*)$/)) + { + $output_filename = $cwd."/".$output_filename; + } +} + +# Do something +if ($initial) +{ + foreach (@data_directory) + { + gen_initial_info($_); + } +} +else +{ + foreach (@data_directory) + { + gen_info($_); + } +} +info("Finished .info-file creation\n"); + +exit(0); + + + +# +# print_usage(handle) +# +# Print usage information. +# + +sub print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE <<END_OF_USAGE); +Usage: $tool_name [OPTIONS] DIRECTORY + +Traverse DIRECTORY and create a .info file for each data file found. Note +that you may specify more than one directory, all of which are then processed +sequentially. + + -h, --help Print this help, then exit + -v, --version Print version number, then exit + -q, --quiet Do not print progress messages + -i, --initial Capture initial zero coverage data + -t, --test-name NAME Use test case name NAME for resulting data + -o, --output-filename OUTFILE Write data only to OUTFILE + -f, --follow Follow links when searching .da/.gcda files + -b, --base-directory DIR Use DIR as base directory for relative paths + --(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 + +For more information see: $lcov_url +END_OF_USAGE + ; +} + + +# +# gen_info(directory) +# +# Traverse DIRECTORY and create a .info file for each data file found. +# The .info file contains TEST_NAME in the following format: +# +# TN:<test name> +# +# For each source file name referenced in the data file, there is a section +# containing source code and coverage data: +# +# SF:<absolute path to the source file> +# FN:<line number of function start>,<function name> for each function +# DA:<line number>,<execution count> for each instrumented line +# LH:<number of lines with an execution count> greater than 0 +# LF:<number of instrumented lines> +# +# Sections are separated by: +# +# end_of_record +# +# In addition to the main source code file there are sections for each +# #included file containing executable code. Note that the absolute path +# of a source file is generated by interpreting the contents of the respective +# graph file. Relative filenames are prefixed with the directory in which the +# graph file is found. Note also that symbolic links to the graph file will be +# resolved so that the actual file path is used instead of the path to a link. +# This approach is necessary for the mechanism to work with the /proc/gcov +# files. +# +# Die on error. +# + +sub gen_info($) +{ + my $directory = $_[0]; + my @file_list; + + if (-d $directory) + { + info("Scanning $directory for $data_file_extension ". + "files ...\n"); + + @file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -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); + } + else + { + @file_list = ($directory); + } + + # Process all files in list + foreach (@file_list) { process_dafile($_); } +} + + +# +# process_dafile(da_filename) +# +# Create a .info file for a single data file. +# +# Die on error. +# + +sub process_dafile($) +{ + info("Processing %s\n", $_[0]); + + 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 $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_functions; # Function calls of a .gcov file + my @gcov_list; # List of generated .gcov files + my $line_number; # Line number count + my $lines_hit; # Number of instrumented lines hit + 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 $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 @result; + my $index; + my $da_renamed; # If data file is to be renamed + local *INFO_HANDLE; + + # Get path to data file in absolute and normalized form (begins with /, + # contains no more ../ or ./) + $da_filename = solve_relative_path($cwd, $_[0]); + + # 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; + } + + if (-z $da_filename) + { + $da_renamed = 1; + } + else + { + $da_renamed = 0; + } + + # Construct base_dir for current file + if ($base_directory) + { + $base_dir = $base_directory; + } + else + { + $base_dir = $source_dir; + } + + # Check for writable $base_dir (gcov will try to write files there) + stat($base_dir); + if (!-w _) + { + die("ERROR: cannot write to directory $base_dir!\n"); + } + + # Construct name of graph file + $bb_filename = $da_dir."/".$da_basename.$graph_file_extension; + + # Find out the real location of graph file in case we're just looking at + # a link + while (readlink($bb_filename)) + { + my $last_dir = dirname($bb_filename); + + $bb_filename = readlink($bb_filename); + $bb_filename = solve_relative_path($last_dir, $bb_filename); + } + + # Ignore empty graph file (e.g. source file with no statement) + if (-z $bb_filename) + { + warn("WARNING: empty $bb_filename (skipped)\n"); + return; + } + + # Read contents of graph file into hash. We need it later to find out + # the absolute path to each .gcov file created as well as for + # information about functions and their source code positions. + if ($gcov_version < $GCOV_VERSION_3_4_0) + { + if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + { + %bb_content = read_hammer_bbg_file($bb_filename, + $base_dir); + } + else + { + %bb_content = read_bb_file($bb_filename, $base_dir); + } + } + else + { + %bb_content = read_gcno_file($bb_filename, $base_dir); + } + + # 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. + $object_dir = dirname($bb_filename); + + # Is the data file in a different directory? (this happens e.g. with + # the gcov-kernel patch) + if ($object_dir ne $da_dir) + { + # Need to create link to data file in $object_dir + system("ln", "-s", $da_filename, + "$object_dir/$da_basename$data_file_extension") + and die ("ERROR: cannot create link $object_dir/". + "$da_basename$data_file_extension!\n"); + } + + # Change to directory containing data files and apply GCOV + chdir($base_dir); + + if ($da_renamed) + { + # Need to rename empty data file to workaround + # gcov <= 3.2.x bug (Abort) + system_no_output(3, "mv", "$da_filename", "$da_filename.ori") + and die ("ERROR: cannot rename $da_filename\n"); + } + + # 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"); + } + + if ($da_renamed) + { + system_no_output(3, "mv", "$da_filename.ori", "$da_filename") + 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); + } + + if ($gcov_error) + { + if ($ignore[$ERROR_GCOV]) + { + warn("WARNING: GCOV failed for $da_filename!\n"); + return; + } + die("ERROR: GCOV failed for $da_filename!\n"); + } + + # Collect data from resulting .gcov files and create .info file + @gcov_list = glob("*.gcov"); + + # Check for files + if (!@gcov_list) + { + warn("WARNING: gcov did not create any files for ". + "$da_filename!\n"); + } + + # Check whether we're writing to a single file + if ($output_filename) + { + if ($output_filename eq "-") + { + *INFO_HANDLE = *STDOUT; + } + else + { + # Append to output file + open(INFO_HANDLE, ">>$output_filename") + or die("ERROR: cannot write to ". + "$output_filename!\n"); + } + } + else + { + # Open .info file for output + open(INFO_HANDLE, ">$da_filename.info") + or die("ERROR: cannot create $da_filename.info!\n"); + } + + # Write test name + printf(INFO_HANDLE "TN:%s\n", $test_name); + + # Traverse the list of generated .gcov files and combine them into a + # single .info file + @unprocessed = keys(%bb_content); + foreach $gcov_file (@gcov_list) + { + ($source, $object) = read_gcov_header($gcov_file); + + if (defined($source)) + { + $source = solve_relative_path($base_dir, $source); + } + + # 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 ($ignore[$ERROR_SOURCE]) + { + warn("WARNING: could not read source file ". + "$source\n"); + next; + } + die("ERROR: could not read source file $source\n"); + } + + @matches = match_filename(defined($source) ? $source : + $gcov_file, keys(%bb_content)); + + # Skip files that are not mentioned in the graph file + if (!@matches) + { + warn("WARNING: cannot find an entry for ".$gcov_file. + " in $graph_file_extension file, skipping ". + "file!\n"); + unlink($gcov_file); + next; + } + + # Read in contents of gcov file + @result = read_gcov_file($gcov_file); + @gcov_content = @{$result[0]}; + @gcov_branches = @{$result[1]}; + @gcov_functions = @{$result[2]}; + + # Skip empty files + if (!@gcov_content) + { + warn("WARNING: skipping empty file ".$gcov_file."\n"); + unlink($gcov_file); + next; + } + + if (scalar(@matches) == 1) + { + # Just one match + $source_filename = $matches[0]; + } + else + { + # Try to solve the ambiguity + $source_filename = solve_ambiguous_match($gcov_file, + \@matches, \@gcov_content); + } + + # Remove processed file from list + for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--) + { + if ($unprocessed[$index] eq $source_filename) + { + splice(@unprocessed, $index, 1); + last; + } + } + + # Write absolute path of source file + printf(INFO_HANDLE "SF:%s\n", $source_filename); + + # Write function-related information + if (defined($bb_content{$source_filename})) + { + foreach (split(",",$bb_content{$source_filename})) + { + my ($fn, $line) = split("=", $_); + + if ($fn eq "") { + next; + } + + # Normalize function name + $fn =~ s/\W/_/g; + + print(INFO_HANDLE "FN:$line,$fn\n"); + } + } + + #-- + #-- FNDA: <call-count>, <function-name> + #-- FNF: overall count of functions + #-- FNH: overall count of functions with non-zero call count + #-- + $funcs_found = 0; + $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); + } + if ($funcs_found > 0) { + printf(INFO_HANDLE "FNF:%s\n", $funcs_found); + printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); + } + + # Reset line counters + $line_number = 0; + $lines_found = 0; + $lines_hit = 0; + + # Write coverage information for each instrumented line + # Note: @gcov_content contains a list of (flag, count, source) + # tuple for each source code line + while (@gcov_content) + { + $line_number++; + + # Check for instrumented line + if ($gcov_content[0]) + { + $lines_found++; + printf(INFO_HANDLE "DA:".$line_number.",". + $gcov_content[1].($checksum ? + ",". md5_base64($gcov_content[2]) : ""). + "\n"); + + # Increase $lines_hit in case of an execution + # count>0 + if ($gcov_content[1] > 0) { $lines_hit++; } + } + + # Remove already processed data from array + 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); + print(INFO_HANDLE "end_of_record\n"); + + # Remove .gcov file after processing + unlink($gcov_file); + } + + # Check for files which show up in the graph file but were never + # processed + if (@unprocessed && @gcov_list) + { + foreach (@unprocessed) + { + warn("WARNING: no data found for $_\n"); + } + } + + if (!($output_filename && ($output_filename eq "-"))) + { + close(INFO_HANDLE); + } + + # Change back to initial directory + chdir($cwd); +} + + +# +# solve_relative_path(path, dir) +# +# Solve relative path components of DIR which, if not absolute, resides in PATH. +# + +sub solve_relative_path($$) +{ + my $path = $_[0]; + my $dir = $_[1]; + my $result; + + $result = $dir; + # Prepend path if not absolute + if ($dir =~ /^[^\/]/) + { + $result = "$path/$result"; + } + + # Remove // + $result =~ s/\/\//\//g; + + # Remove . + $result =~ s/\/\.\//\//g; + + # Solve .. + while ($result =~ s/\/[^\/]+\/\.\.\//\//) + { + } + + # Remove preceding .. + $result =~ s/^\/\.\.\//\//g; + + return $result; +} + + +# +# match_filename(gcov_filename, list) +# +# Return a list of those entries of LIST which match the relative filename +# GCOV_FILENAME. +# + +sub match_filename($@) +{ + my $filename = shift; + my @list = @_; + my @result; + + $filename =~ s/^(.*).gcov$/$1/; + + if ($filename =~ /^\/(.*)$/) + { + $filename = "$1"; + } + + foreach (@list) + { + if (/\/\Q$filename\E(.*)$/ && $1 eq "") + { + @result = (@result, $_); + } + } + return @result; +} + + +# +# solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) +# +# Try to solve ambiguous matches of mapping (gcov file) -> (source code) file +# by comparing source code provided in the GCOV file with that of the files +# in MATCHES. REL_FILENAME identifies the relative filename of the gcov +# file. +# +# Return the one real match or die if there is none. +# + +sub solve_ambiguous_match($$$) +{ + my $rel_name = $_[0]; + my $matches = $_[1]; + my $content = $_[2]; + my $filename; + my $index; + my $no_match; + local *SOURCE; + + # Check the list of matches + foreach $filename (@$matches) + { + + # Compare file contents + open(SOURCE, $filename) + or die("ERROR: cannot read $filename!\n"); + + $no_match = 0; + for ($index = 2; <SOURCE>; $index += 3) + { + chomp; + + if ($_ ne @$content[$index]) + { + $no_match = 1; + last; + } + } + + close(SOURCE); + + if (!$no_match) + { + info("Solved source file ambiguity for $rel_name\n"); + return $filename; + } + } + + die("ERROR: could not match gcov data for $rel_name!\n"); +} + + +# +# split_filename(filename) +# +# Return (path, filename, extension) for a given FILENAME. +# + +sub split_filename($) +{ + my @path_components = split('/', $_[0]); + my @file_components = split('\.', pop(@path_components)); + my $extension = pop(@file_components); + + return (join("/",@path_components), join(".",@file_components), + $extension); +} + + +# +# 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 +# information: +# +# (source, object) +# +# where: +# +# source: complete relative path of the source code file (gcc >= 3.3 only) +# object: name of associated graph file +# +# Die on error. +# + +sub read_gcov_header($) +{ + my $source; + my $object; + local *INPUT; + + if (!open(INPUT, $_[0])) + { + if ($ignore_errors[$ERROR_GCOV]) + { + warn("WARNING: cannot read $_[0]!\n"); + return (undef,undef); + } + die("ERROR: cannot read $_[0]!\n"); + } + + while (<INPUT>) + { + chomp($_); + + if (/^\s+-:\s+0:Source:(.*)$/) + { + # Source: header entry + $source = $1; + } + elsif (/^\s+-:\s+0:Object:(.*)$/) + { + # Object: header entry + $object = $1; + } + else + { + last; + } + } + + close(INPUT); + + return ($source, $object); +} + + +# +# read_gcov_file(gcov_filename) +# +# Parse file GCOV_FILENAME (.gcov file format) and return the list: +# (reference to gcov_content, reference to gcov_branch, reference to gcov_func) +# +# gcov_content is a list of 3 elements +# (flag, count, source) for each source code line: +# +# $result[($line_number-1)*3+0] = instrumentation flag for line $line_number +# $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_func is a list of 2 elements +# (number of calls, function name) for each function +# +# Die on error. +# + +sub read_gcov_file($) +{ + my $filename = $_[0]; + my @result = (); + my @branches = (); + my @functions = (); + my $number; + local *INPUT; + + open(INPUT, $filename) + or die("ERROR: cannot read $filename!\n"); + + if ($gcov_version < $GCOV_VERSION_3_3_0) + { + # Expect gcov format as used in gcc < 3.3 + while (<INPUT>) + { + 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); + } + } + elsif (/^call/ || /^function/) + { + # Function call return data + } + else + { + # Source code execution data + $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); + push(@result, substr($_, 16)); + } + } + } + else + { + # Expect gcov format as used in gcc >= 3.3 + while (<INPUT>) + { + 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); + } + } + elsif (/^function\s+(\S+)\s+called\s+(\d+)/) + { + push(@functions, $2, $1); + } + elsif (/^call/) + { + # Function call return data + } + elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) + { + # <exec count>:<line number>:<source code> + if ($2 eq "0") + { + # Extra data + } + elsif ($1 eq "-") + { + # Uninstrumented line + push(@result, 0); + push(@result, 0); + push(@result, $3); + } + 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} = ""; + } + } + $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); + } + + 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])); +} + + +# +# Get the GCOV tool version. Return an integer number which represents the +# GCOV version. Version numbers can be compared using standard integer +# operations. +# + +sub get_gcov_version() +{ + local *HANDLE; + my $version_string; + my $result; + + open(GCOV_PIPE, "$gcov_tool -v |") + or die("ERROR: cannot retrieve gcov version!\n"); + $version_string = <GCOV_PIPE>; + close(GCOV_PIPE); + + $result = 0; + if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) + { + if (defined($4)) + { + info("Found gcov version: $1.$2.$4\n"); + $result = $1 << 16 | $2 << 8 | $4; + } + else + { + info("Found gcov version: $1.$2\n"); + $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; +} + + +# +# info(printf_parameter) +# +# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag +# is not set. +# + +sub info(@) +{ + if (!$quiet) + { + # Print info string + if (defined($output_filename) && ($output_filename eq "-")) + { + # Don't interfere with the .info output to STDOUT + printf(STDERR @_); + } + else + { + printf(@_); + } + } +} + + +# +# int_handler() +# +# Called when the script was interrupted by an INT signal (e.g. CTRl-C) +# + +sub int_handler() +{ + if ($cwd) { chdir($cwd); } + info("Aborted.\n"); + exit(1); +} + + +# +# system_no_output(mode, parameters) +# +# Call an external program using PARAMETERS while suppressing depending on +# the value of MODE: +# +# MODE & 1: suppress STDOUT +# MODE & 2: suppress STDERR +# +# Return 0 on success, non-zero otherwise. +# + +sub system_no_output($@) +{ + my $mode = shift; + my $result; + local *OLD_STDERR; + local *OLD_STDOUT; + + # Save old stdout and stderr handles + ($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"); + + system(@_); + $result = $?; + + # Close redirected handles + ($mode & 1) && close(STDOUT); + ($mode & 2) && close(STDERR); + + # Restore old handles + ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); + + return $result; +} + + +# +# read_config(filename) +# +# Read configuration file FILENAME and return a reference to a hash containing +# all valid key=value pairs found. +# + +sub read_config($) +{ + my $filename = $_[0]; + my %result; + my $key; + my $value; + local *HANDLE; + + if (!open(HANDLE, "<$filename")) + { + warn("WARNING: cannot read configuration file $filename\n"); + return undef; + } + while (<HANDLE>) + { + chomp; + # Skip comments + s/#.*//; + # Remove leading blanks + s/^\s+//; + # Remove trailing blanks + s/\s+$//; + next unless length; + ($key, $value) = split(/\s*=\s*/, $_, 2); + if (defined($key) && defined($value)) + { + $result{$key} = $value; + } + else + { + warn("WARNING: malformed statement in line $. ". + "of configuration file $filename\n"); + } + } + close(HANDLE); + return \%result; +} + + +# +# apply_config(REF) +# +# REF is a reference to a hash containing the following mapping: +# +# 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. +# + +sub apply_config($) +{ + my $ref = $_[0]; + + foreach (keys(%{$ref})) + { + if (defined($config->{$_})) + { + ${$ref->{$_}} = $config->{$_}; + } + } +} + + +sub gen_initial_info($) +{ + my $directory = $_[0]; + my @file_list; + + if (-d $directory) + { + info("Scanning $directory for $graph_file_extension ". + "files ...\n"); + + @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); + } + else + { + @file_list = ($directory); + } + + # Process all files in list + foreach (@file_list) { process_graphfile($_); } +} + +sub process_graphfile($) +{ + my $graph_filename = $_[0]; + my $graph_dir; + my $graph_basename; + my $source_dir; + my $base_dir; + my %graph_data; + my $filename; + local *INFO_HANDLE; + + info("Processing $_[0]\n"); + + # Get path to data file in absolute and normalized form (begins with /, + # contains no more ../ or ./) + $graph_filename = solve_relative_path($cwd, $graph_filename); + + # 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; + } + + # Construct base_dir for current file + if ($base_directory) + { + $base_dir = $base_directory; + } + else + { + $base_dir = $source_dir; + } + + if ($gcov_version < $GCOV_VERSION_3_4_0) + { + if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) + { + %graph_data = read_hammer_bbg_file($graph_filename, + $base_dir); + } + else + { + %graph_data = read_bb_file($graph_filename, $base_dir); + } + } + else + { + %graph_data = read_gcno_file($graph_filename, $base_dir); + } + + # Check whether we're writing to a single file + if ($output_filename) + { + if ($output_filename eq "-") + { + *INFO_HANDLE = *STDOUT; + } + else + { + # Append to output file + open(INFO_HANDLE, ">>$output_filename") + or die("ERROR: cannot write to ". + "$output_filename!\n"); + } + } + else + { + # Open .info file for output + 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)) + { + my %lines; + my $count = 0; + my @functions; + + 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; + } + + # Normalize function name + $fn =~ s/\W/_/g; + + print(INFO_HANDLE "FN:$line,$fn\n"); + push(@functions, $fn); + } + foreach (@functions) { + print(INFO_HANDLE "FNDA:$_,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(INFO_HANDLE "LH:0\n"); + print(INFO_HANDLE "LF:$count\n"); + print(INFO_HANDLE "end_of_record\n"); + } + if (!($output_filename && ($output_filename eq "-"))) + { + close(INFO_HANDLE); + } +} + +sub warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub die_handler($) +{ + my ($msg) = @_; + + die("$tool_name: $msg"); +} |