Init uninitialized member in ctor.
[chromium-blink-merge.git] / third_party / lcov / bin / geninfo
blob9325f9d68c2d1cfb879d534a09951c70d12f9958
1 #!/usr/bin/perl -w
3 # Copyright (c) International Business Machines Corp., 2002,2012
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or (at
8 # your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # geninfo
22 # This script generates .info files from data files as created by code
23 # instrumented with gcc's built-in profiling mechanism. Call it with
24 # --help and refer to the geninfo man page to get information on usage
25 # and available options.
28 # Authors:
29 # 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
30 # IBM Lab Boeblingen
31 # based on code by Manoj Iyer <manjo@mail.utexas.edu> and
32 # Megan Bock <mbock@us.ibm.com>
33 # IBM Austin
34 # 2002-09-05 / Peter Oberparleiter: implemented option that allows file list
35 # 2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also
36 # parse the new gcov format which is to be introduced in gcc 3.3
37 # 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT
38 # 2003-07-03 / Peter Oberparleiter: added line checksum support, added
39 # --no-checksum
40 # 2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV
41 # 2003-12-11 / Laurent Deniel: added --follow option
42 # workaround gcov (<= 3.2.x) bug with empty .da files
43 # 2004-01-03 / Laurent Deniel: Ignore empty .bb files
44 # 2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and
45 # gcov versioning
46 # 2004-08-09 / Peter Oberparleiter: added configuration file support
47 # 2008-07-14 / Tom Zoerner: added --function-coverage command line option
48 # 2008-08-13 / Peter Oberparleiter: modified function coverage
49 # implementation (now enabled per default)
52 use strict;
53 use File::Basename;
54 use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir
55 splitpath catpath/;
56 use Getopt::Long;
57 use Digest::MD5 qw(md5_base64);
58 if( $^O eq "msys" )
60 require File::Spec::Win32;
63 # Constants
64 our $lcov_version = 'LCOV version 1.10';
65 our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php";
66 our $gcov_tool = "gcov";
67 our $tool_name = basename($0);
69 our $GCOV_VERSION_4_7_0 = 0x40700;
70 our $GCOV_VERSION_3_4_0 = 0x30400;
71 our $GCOV_VERSION_3_3_0 = 0x30300;
72 our $GCNO_FUNCTION_TAG = 0x01000000;
73 our $GCNO_LINES_TAG = 0x01450000;
74 our $GCNO_FILE_MAGIC = 0x67636e6f;
75 our $BBG_FILE_MAGIC = 0x67626267;
77 # Error classes which users may specify to ignore during processing
78 our $ERROR_GCOV = 0;
79 our $ERROR_SOURCE = 1;
80 our $ERROR_GRAPH = 2;
81 our %ERROR_ID = (
82 "gcov" => $ERROR_GCOV,
83 "source" => $ERROR_SOURCE,
84 "graph" => $ERROR_GRAPH,
87 our $EXCL_START = "LCOV_EXCL_START";
88 our $EXCL_STOP = "LCOV_EXCL_STOP";
89 our $EXCL_LINE = "LCOV_EXCL_LINE";
91 # Compatibility mode values
92 our $COMPAT_VALUE_OFF = 0;
93 our $COMPAT_VALUE_ON = 1;
94 our $COMPAT_VALUE_AUTO = 2;
96 # Compatibility mode value names
97 our %COMPAT_NAME_TO_VALUE = (
98 "off" => $COMPAT_VALUE_OFF,
99 "on" => $COMPAT_VALUE_ON,
100 "auto" => $COMPAT_VALUE_AUTO,
103 # Compatiblity modes
104 our $COMPAT_MODE_LIBTOOL = 1 << 0;
105 our $COMPAT_MODE_HAMMER = 1 << 1;
106 our $COMPAT_MODE_SPLIT_CRC = 1 << 2;
108 # Compatibility mode names
109 our %COMPAT_NAME_TO_MODE = (
110 "libtool" => $COMPAT_MODE_LIBTOOL,
111 "hammer" => $COMPAT_MODE_HAMMER,
112 "split_crc" => $COMPAT_MODE_SPLIT_CRC,
113 "android_4_4_0" => $COMPAT_MODE_SPLIT_CRC,
116 # Map modes to names
117 our %COMPAT_MODE_TO_NAME = (
118 $COMPAT_MODE_LIBTOOL => "libtool",
119 $COMPAT_MODE_HAMMER => "hammer",
120 $COMPAT_MODE_SPLIT_CRC => "split_crc",
123 # Compatibility mode default values
124 our %COMPAT_MODE_DEFAULTS = (
125 $COMPAT_MODE_LIBTOOL => $COMPAT_VALUE_ON,
126 $COMPAT_MODE_HAMMER => $COMPAT_VALUE_AUTO,
127 $COMPAT_MODE_SPLIT_CRC => $COMPAT_VALUE_AUTO,
130 # Compatibility mode auto-detection routines
131 sub compat_hammer_autodetect();
132 our %COMPAT_MODE_AUTO = (
133 $COMPAT_MODE_HAMMER => \&compat_hammer_autodetect,
134 $COMPAT_MODE_SPLIT_CRC => 1, # will be done later
137 our $BR_LINE = 0;
138 our $BR_BLOCK = 1;
139 our $BR_BRANCH = 2;
140 our $BR_TAKEN = 3;
141 our $BR_VEC_ENTRIES = 4;
142 our $BR_VEC_WIDTH = 32;
144 our $UNNAMED_BLOCK = 9999;
146 # Prototypes
147 sub print_usage(*);
148 sub gen_info($);
149 sub process_dafile($$);
150 sub match_filename($@);
151 sub solve_ambiguous_match($$$);
152 sub split_filename($);
153 sub solve_relative_path($$);
154 sub read_gcov_header($);
155 sub read_gcov_file($);
156 sub info(@);
157 sub get_gcov_version();
158 sub system_no_output($@);
159 sub read_config($);
160 sub apply_config($);
161 sub get_exclusion_data($);
162 sub apply_exclusion_data($$);
163 sub process_graphfile($$);
164 sub filter_fn_name($);
165 sub warn_handler($);
166 sub die_handler($);
167 sub graph_error($$);
168 sub graph_expect($);
169 sub graph_read(*$;$$);
170 sub graph_skip(*$;$);
171 sub sort_uniq(@);
172 sub sort_uniq_lex(@);
173 sub graph_cleanup($);
174 sub graph_find_base($);
175 sub graph_from_bb($$$);
176 sub graph_add_order($$$);
177 sub read_bb_word(*;$);
178 sub read_bb_value(*;$);
179 sub read_bb_string(*$);
180 sub read_bb($);
181 sub read_bbg_word(*;$);
182 sub read_bbg_value(*;$);
183 sub read_bbg_string(*);
184 sub read_bbg_lines_record(*$$$$$);
185 sub read_bbg($);
186 sub read_gcno_word(*;$$);
187 sub read_gcno_value(*$;$$);
188 sub read_gcno_string(*$);
189 sub read_gcno_lines_record(*$$$$$$);
190 sub determine_gcno_split_crc($$$);
191 sub read_gcno_function_record(*$$$$);
192 sub read_gcno($);
193 sub get_gcov_capabilities();
194 sub get_overall_line($$$$);
195 sub print_overall_rate($$$$$$$$$);
196 sub br_gvec_len($);
197 sub br_gvec_get($$);
198 sub debug($);
199 sub int_handler();
200 sub parse_ignore_errors(@);
201 sub is_external($);
202 sub compat_name($);
203 sub parse_compat_modes($);
204 sub is_compat($);
205 sub is_compat_auto($);
208 # Global variables
209 our $gcov_version;
210 our $gcov_version_string;
211 our $graph_file_extension;
212 our $data_file_extension;
213 our @data_directory;
214 our $test_name = "";
215 our $quiet;
216 our $help;
217 our $output_filename;
218 our $base_directory;
219 our $version;
220 our $follow;
221 our $checksum;
222 our $no_checksum;
223 our $opt_compat_libtool;
224 our $opt_no_compat_libtool;
225 our $rc_adjust_src_path;# Regexp specifying parts to remove from source path
226 our $adjust_src_pattern;
227 our $adjust_src_replace;
228 our $adjust_testname;
229 our $config; # Configuration file contents
230 our @ignore_errors; # List of errors to ignore (parameter)
231 our @ignore; # List of errors to ignore (array)
232 our $initial;
233 our $no_recursion = 0;
234 our $maxdepth;
235 our $no_markers = 0;
236 our $opt_derive_func_data = 0;
237 our $opt_external = 1;
238 our $opt_no_external;
239 our $debug = 0;
240 our $gcov_caps;
241 our @gcov_options;
242 our @internal_dirs;
243 our $opt_config_file;
244 our $opt_gcov_all_blocks = 1;
245 our $opt_compat;
246 our %opt_rc;
247 our %compat_value;
248 our $gcno_split_crc;
249 our $func_coverage = 1;
250 our $br_coverage = 0;
251 our $rc_auto_base = 1;
253 our $cwd = `pwd`;
254 chomp($cwd);
258 # Code entry point
261 # Register handler routine to be called when interrupted
262 $SIG{"INT"} = \&int_handler;
263 $SIG{__WARN__} = \&warn_handler;
264 $SIG{__DIE__} = \&die_handler;
266 # Prettify version string
267 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
269 # Set LANG so that gcov output will be in a unified format
270 $ENV{"LANG"} = "C";
272 # Check command line for a configuration file name
273 Getopt::Long::Configure("pass_through", "no_auto_abbrev");
274 GetOptions("config-file=s" => \$opt_config_file,
275 "rc=s%" => \%opt_rc);
276 Getopt::Long::Configure("default");
278 # Read configuration file if available
279 if (defined($opt_config_file)) {
280 $config = read_config($opt_config_file);
281 } elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
283 $config = read_config($ENV{"HOME"}."/.lcovrc");
285 elsif (-r "/etc/lcovrc")
287 $config = read_config("/etc/lcovrc");
290 if ($config || %opt_rc)
292 # Copy configuration file and --rc values to variables
293 apply_config({
294 "geninfo_gcov_tool" => \$gcov_tool,
295 "geninfo_adjust_testname" => \$adjust_testname,
296 "geninfo_checksum" => \$checksum,
297 "geninfo_no_checksum" => \$no_checksum, # deprecated
298 "geninfo_compat_libtool" => \$opt_compat_libtool,
299 "geninfo_external" => \$opt_external,
300 "geninfo_gcov_all_blocks" => \$opt_gcov_all_blocks,
301 "geninfo_compat" => \$opt_compat,
302 "geninfo_adjust_src_path" => \$rc_adjust_src_path,
303 "geninfo_auto_base" => \$rc_auto_base,
304 "lcov_function_coverage" => \$func_coverage,
305 "lcov_branch_coverage" => \$br_coverage,
308 # Merge options
309 if (defined($no_checksum))
311 $checksum = ($no_checksum ? 0 : 1);
312 $no_checksum = undef;
315 # Check regexp
316 if (defined($rc_adjust_src_path)) {
317 my ($pattern, $replace) = split(/\s*=>\s*/,
318 $rc_adjust_src_path);
319 local $SIG{__DIE__};
320 eval '$adjust_src_pattern = qr>'.$pattern.'>;';
321 if (!defined($adjust_src_pattern)) {
322 my $msg = $@;
324 chomp($msg);
325 $msg =~ s/at \(eval.*$//;
326 warn("WARNING: invalid pattern in ".
327 "geninfo_adjust_src_path: $msg\n");
328 } elsif (!defined($replace)) {
329 # If no replacement is specified, simply remove pattern
330 $adjust_src_replace = "";
331 } else {
332 $adjust_src_replace = $replace;
337 # Parse command line options
338 if (!GetOptions("test-name|t=s" => \$test_name,
339 "output-filename|o=s" => \$output_filename,
340 "checksum" => \$checksum,
341 "no-checksum" => \$no_checksum,
342 "base-directory|b=s" => \$base_directory,
343 "version|v" =>\$version,
344 "quiet|q" => \$quiet,
345 "help|h|?" => \$help,
346 "follow|f" => \$follow,
347 "compat-libtool" => \$opt_compat_libtool,
348 "no-compat-libtool" => \$opt_no_compat_libtool,
349 "gcov-tool=s" => \$gcov_tool,
350 "ignore-errors=s" => \@ignore_errors,
351 "initial|i" => \$initial,
352 "no-recursion" => \$no_recursion,
353 "no-markers" => \$no_markers,
354 "derive-func-data" => \$opt_derive_func_data,
355 "debug" => \$debug,
356 "external" => \$opt_external,
357 "no-external" => \$opt_no_external,
358 "compat=s" => \$opt_compat,
359 "config-file=s" => \$opt_config_file,
360 "rc=s%" => \%opt_rc,
363 print(STDERR "Use $tool_name --help to get usage information\n");
364 exit(1);
366 else
368 # Merge options
369 if (defined($no_checksum))
371 $checksum = ($no_checksum ? 0 : 1);
372 $no_checksum = undef;
375 if (defined($opt_no_compat_libtool))
377 $opt_compat_libtool = ($opt_no_compat_libtool ? 0 : 1);
378 $opt_no_compat_libtool = undef;
381 if (defined($opt_no_external)) {
382 $opt_external = 0;
383 $opt_no_external = undef;
387 @data_directory = @ARGV;
389 # Check for help option
390 if ($help)
392 print_usage(*STDOUT);
393 exit(0);
396 # Check for version option
397 if ($version)
399 print("$tool_name: $lcov_version\n");
400 exit(0);
403 # Check gcov tool
404 if (system_no_output(3, $gcov_tool, "--help") == -1)
406 die("ERROR: need tool $gcov_tool!\n");
409 ($gcov_version, $gcov_version_string) = get_gcov_version();
411 # Determine gcov options
412 $gcov_caps = get_gcov_capabilities();
413 push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'} &&
414 ($br_coverage || $func_coverage));
415 push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'} &&
416 $br_coverage);
417 push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'} &&
418 $opt_gcov_all_blocks && $br_coverage);
419 push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'});
421 # Determine compatibility modes
422 parse_compat_modes($opt_compat);
424 # Determine which errors the user wants us to ignore
425 parse_ignore_errors(@ignore_errors);
427 # Make sure test names only contain valid characters
428 if ($test_name =~ s/\W/_/g)
430 warn("WARNING: invalid characters removed from testname!\n");
433 # Adjust test name to include uname output if requested
434 if ($adjust_testname)
436 $test_name .= "__".`uname -a`;
437 $test_name =~ s/\W/_/g;
440 # Make sure base_directory contains an absolute path specification
441 if ($base_directory)
443 $base_directory = solve_relative_path($cwd, $base_directory);
446 # Check for follow option
447 if ($follow)
449 $follow = "-follow"
451 else
453 $follow = "";
456 # Determine checksum mode
457 if (defined($checksum))
459 # Normalize to boolean
460 $checksum = ($checksum ? 1 : 0);
462 else
464 # Default is off
465 $checksum = 0;
468 # Determine max depth for recursion
469 if ($no_recursion)
471 $maxdepth = "-maxdepth 1";
473 else
475 $maxdepth = "";
478 # Check for directory name
479 if (!@data_directory)
481 die("No directory specified\n".
482 "Use $tool_name --help to get usage information\n");
484 else
486 foreach (@data_directory)
488 stat($_);
489 if (!-r _)
491 die("ERROR: cannot read $_!\n");
496 if ($gcov_version < $GCOV_VERSION_3_4_0)
498 if (is_compat($COMPAT_MODE_HAMMER))
500 $data_file_extension = ".da";
501 $graph_file_extension = ".bbg";
503 else
505 $data_file_extension = ".da";
506 $graph_file_extension = ".bb";
509 else
511 $data_file_extension = ".gcda";
512 $graph_file_extension = ".gcno";
515 # Check output filename
516 if (defined($output_filename) && ($output_filename ne "-"))
518 # Initially create output filename, data is appended
519 # for each data file processed
520 local *DUMMY_HANDLE;
521 open(DUMMY_HANDLE, ">", $output_filename)
522 or die("ERROR: cannot create $output_filename!\n");
523 close(DUMMY_HANDLE);
525 # Make $output_filename an absolute path because we're going
526 # to change directories while processing files
527 if (!($output_filename =~ /^\/(.*)$/))
529 $output_filename = $cwd."/".$output_filename;
533 # Build list of directories to identify external files
534 foreach my $entry(@data_directory, $base_directory) {
535 next if (!defined($entry));
536 push(@internal_dirs, solve_relative_path($cwd, $entry));
539 # Do something
540 foreach my $entry (@data_directory) {
541 gen_info($entry);
544 if ($initial && $br_coverage) {
545 warn("Note: --initial does not generate branch coverage ".
546 "data\n");
548 info("Finished .info-file creation\n");
550 exit(0);
555 # print_usage(handle)
557 # Print usage information.
560 sub print_usage(*)
562 local *HANDLE = $_[0];
564 print(HANDLE <<END_OF_USAGE);
565 Usage: $tool_name [OPTIONS] DIRECTORY
567 Traverse DIRECTORY and create a .info file for each data file found. Note
568 that you may specify more than one directory, all of which are then processed
569 sequentially.
571 -h, --help Print this help, then exit
572 -v, --version Print version number, then exit
573 -q, --quiet Do not print progress messages
574 -i, --initial Capture initial zero coverage data
575 -t, --test-name NAME Use test case name NAME for resulting data
576 -o, --output-filename OUTFILE Write data only to OUTFILE
577 -f, --follow Follow links when searching .da/.gcda files
578 -b, --base-directory DIR Use DIR as base directory for relative paths
579 --(no-)checksum Enable (disable) line checksumming
580 --(no-)compat-libtool Enable (disable) libtool compatibility mode
581 --gcov-tool TOOL Specify gcov tool location
582 --ignore-errors ERROR Continue after ERROR (gcov, source, graph)
583 --no-recursion Exclude subdirectories from processing
584 --no-markers Ignore exclusion markers in source code
585 --derive-func-data Generate function data from line data
586 --(no-)external Include (ignore) data for external files
587 --config-file FILENAME Specify configuration file location
588 --rc SETTING=VALUE Override configuration file setting
589 --compat MODE=on|off|auto Set compat MODE (libtool, hammer, split_crc)
591 For more information see: $lcov_url
592 END_OF_USAGE
597 # get_common_prefix(min_dir, filenames)
599 # Return the longest path prefix shared by all filenames. MIN_DIR specifies
600 # the minimum number of directories that a filename may have after removing
601 # the prefix.
604 sub get_common_prefix($@)
606 my ($min_dir, @files) = @_;
607 my $file;
608 my @prefix;
609 my $i;
611 foreach $file (@files) {
612 my ($v, $d, $f) = splitpath($file);
613 my @comp = splitdir($d);
615 if (!@prefix) {
616 @prefix = @comp;
617 next;
619 for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) {
620 if ($comp[$i] ne $prefix[$i] ||
621 ((scalar(@comp) - ($i + 1)) <= $min_dir)) {
622 delete(@prefix[$i..scalar(@prefix)]);
623 last;
628 return catdir(@prefix);
632 # gen_info(directory)
634 # Traverse DIRECTORY and create a .info file for each data file found.
635 # The .info file contains TEST_NAME in the following format:
637 # TN:<test name>
639 # For each source file name referenced in the data file, there is a section
640 # containing source code and coverage data:
642 # SF:<absolute path to the source file>
643 # FN:<line number of function start>,<function name> for each function
644 # DA:<line number>,<execution count> for each instrumented line
645 # LH:<number of lines with an execution count> greater than 0
646 # LF:<number of instrumented lines>
648 # Sections are separated by:
650 # end_of_record
652 # In addition to the main source code file there are sections for each
653 # #included file containing executable code. Note that the absolute path
654 # of a source file is generated by interpreting the contents of the respective
655 # graph file. Relative filenames are prefixed with the directory in which the
656 # graph file is found. Note also that symbolic links to the graph file will be
657 # resolved so that the actual file path is used instead of the path to a link.
658 # This approach is necessary for the mechanism to work with the /proc/gcov
659 # files.
661 # Die on error.
664 sub gen_info($)
666 my $directory = $_[0];
667 my @file_list;
668 my $file;
669 my $prefix;
670 my $type;
671 my $ext;
673 if ($initial) {
674 $type = "graph";
675 $ext = $graph_file_extension;
676 } else {
677 $type = "data";
678 $ext = $data_file_extension;
681 if (-d $directory)
683 info("Scanning $directory for $ext files ...\n");
685 @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`;
686 chomp(@file_list);
687 @file_list or
688 die("ERROR: no $ext files found in $directory!\n");
689 $prefix = get_common_prefix(1, @file_list);
690 info("Found %d %s files in %s\n", $#file_list+1, $type,
691 $directory);
693 else
695 @file_list = ($directory);
696 $prefix = "";
699 # Process all files in list
700 foreach $file (@file_list) {
701 # Process file
702 if ($initial) {
703 process_graphfile($file, $prefix);
704 } else {
705 process_dafile($file, $prefix);
712 # derive_data(contentdata, funcdata, bbdata)
714 # Calculate function coverage data by combining line coverage data and the
715 # list of lines belonging to a function.
717 # contentdata: [ instr1, count1, source1, instr2, count2, source2, ... ]
718 # instr<n>: Instrumentation flag for line n
719 # count<n>: Execution count for line n
720 # source<n>: Source code for line n
722 # funcdata: [ count1, func1, count2, func2, ... ]
723 # count<n>: Execution count for function number n
724 # func<n>: Function name for function number n
726 # bbdata: function_name -> [ line1, line2, ... ]
727 # line<n>: Line number belonging to the corresponding function
730 sub derive_data($$$)
732 my ($contentdata, $funcdata, $bbdata) = @_;
733 my @gcov_content = @{$contentdata};
734 my @gcov_functions = @{$funcdata};
735 my %fn_count;
736 my %ln_fn;
737 my $line;
738 my $maxline;
739 my %fn_name;
740 my $fn;
741 my $count;
743 if (!defined($bbdata)) {
744 return @gcov_functions;
747 # First add existing function data
748 while (@gcov_functions) {
749 $count = shift(@gcov_functions);
750 $fn = shift(@gcov_functions);
752 $fn_count{$fn} = $count;
755 # Convert line coverage data to function data
756 foreach $fn (keys(%{$bbdata})) {
757 my $line_data = $bbdata->{$fn};
758 my $line;
759 my $fninstr = 0;
761 if ($fn eq "") {
762 next;
764 # Find the lowest line count for this function
765 $count = 0;
766 foreach $line (@$line_data) {
767 my $linstr = $gcov_content[ ( $line - 1 ) * 3 + 0 ];
768 my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ];
770 next if (!$linstr);
771 $fninstr = 1;
772 if (($lcount > 0) &&
773 (($count == 0) || ($lcount < $count))) {
774 $count = $lcount;
777 next if (!$fninstr);
778 $fn_count{$fn} = $count;
782 # Check if we got data for all functions
783 foreach $fn (keys(%fn_name)) {
784 if ($fn eq "") {
785 next;
787 if (defined($fn_count{$fn})) {
788 next;
790 warn("WARNING: no derived data found for function $fn\n");
793 # Convert hash to list in @gcov_functions format
794 foreach $fn (sort(keys(%fn_count))) {
795 push(@gcov_functions, $fn_count{$fn}, $fn);
798 return @gcov_functions;
802 # get_filenames(directory, pattern)
804 # Return a list of filenames found in directory which match the specified
805 # pattern.
807 # Die on error.
810 sub get_filenames($$)
812 my ($dirname, $pattern) = @_;
813 my @result;
814 my $directory;
815 local *DIR;
817 opendir(DIR, $dirname) or
818 die("ERROR: cannot read directory $dirname\n");
819 while ($directory = readdir(DIR)) {
820 push(@result, $directory) if ($directory =~ /$pattern/);
822 closedir(DIR);
824 return @result;
828 # process_dafile(da_filename, dir)
830 # Create a .info file for a single data file.
832 # Die on error.
835 sub process_dafile($$)
837 my ($file, $dir) = @_;
838 my $da_filename; # Name of data file to process
839 my $da_dir; # Directory of data file
840 my $source_dir; # Directory of source file
841 my $da_basename; # data filename without ".da/.gcda" extension
842 my $bb_filename; # Name of respective graph file
843 my $bb_basename; # Basename of the original graph file
844 my $graph; # Contents of graph file
845 my $instr; # Contents of graph file part 2
846 my $gcov_error; # Error code of gcov tool
847 my $object_dir; # Directory containing all object files
848 my $source_filename; # Name of a source code file
849 my $gcov_file; # Name of a .gcov file
850 my @gcov_content; # Content of a .gcov file
851 my $gcov_branches; # Branch content of a .gcov file
852 my @gcov_functions; # Function calls of a .gcov file
853 my @gcov_list; # List of generated .gcov files
854 my $line_number; # Line number count
855 my $lines_hit; # Number of instrumented lines hit
856 my $lines_found; # Number of instrumented lines found
857 my $funcs_hit; # Number of instrumented functions hit
858 my $funcs_found; # Number of instrumented functions found
859 my $br_hit;
860 my $br_found;
861 my $source; # gcov source header information
862 my $object; # gcov object header information
863 my @matches; # List of absolute paths matching filename
864 my @unprocessed; # List of unprocessed source code files
865 my $base_dir; # Base directory for current file
866 my @tmp_links; # Temporary links to be cleaned up
867 my @result;
868 my $index;
869 my $da_renamed; # If data file is to be renamed
870 local *INFO_HANDLE;
872 info("Processing %s\n", abs2rel($file, $dir));
873 # Get path to data file in absolute and normalized form (begins with /,
874 # contains no more ../ or ./)
875 $da_filename = solve_relative_path($cwd, $file);
877 # Get directory and basename of data file
878 ($da_dir, $da_basename) = split_filename($da_filename);
880 $source_dir = $da_dir;
881 if (is_compat($COMPAT_MODE_LIBTOOL)) {
882 # Avoid files from .libs dirs
883 $source_dir =~ s/\.libs$//;
886 if (-z $da_filename)
888 $da_renamed = 1;
890 else
892 $da_renamed = 0;
895 # Construct base_dir for current file
896 if ($base_directory)
898 $base_dir = $base_directory;
900 else
902 $base_dir = $source_dir;
905 # Check for writable $base_dir (gcov will try to write files there)
906 stat($base_dir);
907 if (!-w _)
909 die("ERROR: cannot write to directory $base_dir!\n");
912 # Construct name of graph file
913 $bb_basename = $da_basename.$graph_file_extension;
914 $bb_filename = "$da_dir/$bb_basename";
916 # Find out the real location of graph file in case we're just looking at
917 # a link
918 while (readlink($bb_filename))
920 my $last_dir = dirname($bb_filename);
922 $bb_filename = readlink($bb_filename);
923 $bb_filename = solve_relative_path($last_dir, $bb_filename);
926 # Ignore empty graph file (e.g. source file with no statement)
927 if (-z $bb_filename)
929 warn("WARNING: empty $bb_filename (skipped)\n");
930 return;
933 # Read contents of graph file into hash. We need it later to find out
934 # the absolute path to each .gcov file created as well as for
935 # information about functions and their source code positions.
936 if ($gcov_version < $GCOV_VERSION_3_4_0)
938 if (is_compat($COMPAT_MODE_HAMMER))
940 ($instr, $graph) = read_bbg($bb_filename);
942 else
944 ($instr, $graph) = read_bb($bb_filename);
947 else
949 ($instr, $graph) = read_gcno($bb_filename);
952 # Try to find base directory automatically if requested by user
953 if ($rc_auto_base) {
954 $base_dir = find_base_from_graph($base_dir, $instr, $graph);
957 ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph);
959 # Set $object_dir to real location of object files. This may differ
960 # from $da_dir if the graph file is just a link to the "real" object
961 # file location.
962 $object_dir = dirname($bb_filename);
964 # Is the data file in a different directory? (this happens e.g. with
965 # the gcov-kernel patch)
966 if ($object_dir ne $da_dir)
968 # Need to create link to data file in $object_dir
969 system("ln", "-s", $da_filename,
970 "$object_dir/$da_basename$data_file_extension")
971 and die ("ERROR: cannot create link $object_dir/".
972 "$da_basename$data_file_extension!\n");
973 push(@tmp_links,
974 "$object_dir/$da_basename$data_file_extension");
975 # Need to create link to graph file if basename of link
976 # and file are different (CONFIG_MODVERSION compat)
977 if ((basename($bb_filename) ne $bb_basename) &&
978 (! -e "$object_dir/$bb_basename")) {
979 symlink($bb_filename, "$object_dir/$bb_basename") or
980 warn("WARNING: cannot create link ".
981 "$object_dir/$bb_basename\n");
982 push(@tmp_links, "$object_dir/$bb_basename");
986 # Change to directory containing data files and apply GCOV
987 debug("chdir($base_dir)\n");
988 chdir($base_dir);
990 if ($da_renamed)
992 # Need to rename empty data file to workaround
993 # gcov <= 3.2.x bug (Abort)
994 system_no_output(3, "mv", "$da_filename", "$da_filename.ori")
995 and die ("ERROR: cannot rename $da_filename\n");
998 # Execute gcov command and suppress standard output
999 $gcov_error = system_no_output(1, $gcov_tool, $da_filename,
1000 "-o", $object_dir, @gcov_options);
1002 if ($da_renamed)
1004 system_no_output(3, "mv", "$da_filename.ori", "$da_filename")
1005 and die ("ERROR: cannot rename $da_filename.ori");
1008 # Clean up temporary links
1009 foreach (@tmp_links) {
1010 unlink($_);
1013 if ($gcov_error)
1015 if ($ignore[$ERROR_GCOV])
1017 warn("WARNING: GCOV failed for $da_filename!\n");
1018 return;
1020 die("ERROR: GCOV failed for $da_filename!\n");
1023 # Collect data from resulting .gcov files and create .info file
1024 @gcov_list = get_filenames('.', '\.gcov$');
1026 # Check for files
1027 if (!@gcov_list)
1029 warn("WARNING: gcov did not create any files for ".
1030 "$da_filename!\n");
1033 # Check whether we're writing to a single file
1034 if ($output_filename)
1036 if ($output_filename eq "-")
1038 *INFO_HANDLE = *STDOUT;
1040 else
1042 # Append to output file
1043 open(INFO_HANDLE, ">>", $output_filename)
1044 or die("ERROR: cannot write to ".
1045 "$output_filename!\n");
1048 else
1050 # Open .info file for output
1051 open(INFO_HANDLE, ">", "$da_filename.info")
1052 or die("ERROR: cannot create $da_filename.info!\n");
1055 # Write test name
1056 printf(INFO_HANDLE "TN:%s\n", $test_name);
1058 # Traverse the list of generated .gcov files and combine them into a
1059 # single .info file
1060 @unprocessed = keys(%{$instr});
1061 foreach $gcov_file (sort(@gcov_list))
1063 my $i;
1064 my $num;
1066 # Skip gcov file for gcc built-in code
1067 next if ($gcov_file eq "<built-in>.gcov");
1069 ($source, $object) = read_gcov_header($gcov_file);
1071 if (!defined($source)) {
1072 # Derive source file name from gcov file name if
1073 # header format could not be parsed
1074 $source = $gcov_file;
1075 $source =~ s/\.gcov$//;
1078 $source = solve_relative_path($base_dir, $source);
1080 if (defined($adjust_src_pattern)) {
1081 # Apply transformation as specified by user
1082 $source =~ s/$adjust_src_pattern/$adjust_src_replace/g;
1085 # gcov will happily create output even if there's no source code
1086 # available - this interferes with checksum creation so we need
1087 # to pull the emergency brake here.
1088 if (! -r $source && $checksum)
1090 if ($ignore[$ERROR_SOURCE])
1092 warn("WARNING: could not read source file ".
1093 "$source\n");
1094 next;
1096 die("ERROR: could not read source file $source\n");
1099 @matches = match_filename($source, keys(%{$instr}));
1101 # Skip files that are not mentioned in the graph file
1102 if (!@matches)
1104 warn("WARNING: cannot find an entry for ".$gcov_file.
1105 " in $graph_file_extension file, skipping ".
1106 "file!\n");
1107 unlink($gcov_file);
1108 next;
1111 # Read in contents of gcov file
1112 @result = read_gcov_file($gcov_file);
1113 if (!defined($result[0])) {
1114 warn("WARNING: skipping unreadable file ".
1115 $gcov_file."\n");
1116 unlink($gcov_file);
1117 next;
1119 @gcov_content = @{$result[0]};
1120 $gcov_branches = $result[1];
1121 @gcov_functions = @{$result[2]};
1123 # Skip empty files
1124 if (!@gcov_content)
1126 warn("WARNING: skipping empty file ".$gcov_file."\n");
1127 unlink($gcov_file);
1128 next;
1131 if (scalar(@matches) == 1)
1133 # Just one match
1134 $source_filename = $matches[0];
1136 else
1138 # Try to solve the ambiguity
1139 $source_filename = solve_ambiguous_match($gcov_file,
1140 \@matches, \@gcov_content);
1143 # Remove processed file from list
1144 for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--)
1146 if ($unprocessed[$index] eq $source_filename)
1148 splice(@unprocessed, $index, 1);
1149 last;
1153 # Skip external files if requested
1154 if (!$opt_external) {
1155 if (is_external($source_filename)) {
1156 info(" ignoring data for external file ".
1157 "$source_filename\n");
1158 unlink($gcov_file);
1159 next;
1163 # Write absolute path of source file
1164 printf(INFO_HANDLE "SF:%s\n", $source_filename);
1166 # If requested, derive function coverage data from
1167 # line coverage data of the first line of a function
1168 if ($opt_derive_func_data) {
1169 @gcov_functions =
1170 derive_data(\@gcov_content, \@gcov_functions,
1171 $graph->{$source_filename});
1174 # Write function-related information
1175 if (defined($graph->{$source_filename}))
1177 my $fn_data = $graph->{$source_filename};
1178 my $fn;
1180 foreach $fn (sort
1181 {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]}
1182 keys(%{$fn_data})) {
1183 my $ln_data = $fn_data->{$fn};
1184 my $line = $ln_data->[0];
1186 # Skip empty function
1187 if ($fn eq "") {
1188 next;
1190 # Remove excluded functions
1191 if (!$no_markers) {
1192 my $gfn;
1193 my $found = 0;
1195 foreach $gfn (@gcov_functions) {
1196 if ($gfn eq $fn) {
1197 $found = 1;
1198 last;
1201 if (!$found) {
1202 next;
1206 # Normalize function name
1207 $fn = filter_fn_name($fn);
1209 print(INFO_HANDLE "FN:$line,$fn\n");
1214 #-- FNDA: <call-count>, <function-name>
1215 #-- FNF: overall count of functions
1216 #-- FNH: overall count of functions with non-zero call count
1218 $funcs_found = 0;
1219 $funcs_hit = 0;
1220 while (@gcov_functions)
1222 my $count = shift(@gcov_functions);
1223 my $fn = shift(@gcov_functions);
1225 $fn = filter_fn_name($fn);
1226 printf(INFO_HANDLE "FNDA:$count,$fn\n");
1227 $funcs_found++;
1228 $funcs_hit++ if ($count > 0);
1230 if ($funcs_found > 0) {
1231 printf(INFO_HANDLE "FNF:%s\n", $funcs_found);
1232 printf(INFO_HANDLE "FNH:%s\n", $funcs_hit);
1235 # Write coverage information for each instrumented branch:
1237 # BRDA:<line number>,<block number>,<branch number>,<taken>
1239 # where 'taken' is the number of times the branch was taken
1240 # or '-' if the block to which the branch belongs was never
1241 # executed
1242 $br_found = 0;
1243 $br_hit = 0;
1244 $num = br_gvec_len($gcov_branches);
1245 for ($i = 0; $i < $num; $i++) {
1246 my ($line, $block, $branch, $taken) =
1247 br_gvec_get($gcov_branches, $i);
1249 print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n");
1250 $br_found++;
1251 $br_hit++ if ($taken ne '-' && $taken > 0);
1253 if ($br_found > 0) {
1254 printf(INFO_HANDLE "BRF:%s\n", $br_found);
1255 printf(INFO_HANDLE "BRH:%s\n", $br_hit);
1258 # Reset line counters
1259 $line_number = 0;
1260 $lines_found = 0;
1261 $lines_hit = 0;
1263 # Write coverage information for each instrumented line
1264 # Note: @gcov_content contains a list of (flag, count, source)
1265 # tuple for each source code line
1266 while (@gcov_content)
1268 $line_number++;
1270 # Check for instrumented line
1271 if ($gcov_content[0])
1273 $lines_found++;
1274 printf(INFO_HANDLE "DA:".$line_number.",".
1275 $gcov_content[1].($checksum ?
1276 ",". md5_base64($gcov_content[2]) : "").
1277 "\n");
1279 # Increase $lines_hit in case of an execution
1280 # count>0
1281 if ($gcov_content[1] > 0) { $lines_hit++; }
1284 # Remove already processed data from array
1285 splice(@gcov_content,0,3);
1288 # Write line statistics and section separator
1289 printf(INFO_HANDLE "LF:%s\n", $lines_found);
1290 printf(INFO_HANDLE "LH:%s\n", $lines_hit);
1291 print(INFO_HANDLE "end_of_record\n");
1293 # Remove .gcov file after processing
1294 unlink($gcov_file);
1297 # Check for files which show up in the graph file but were never
1298 # processed
1299 if (@unprocessed && @gcov_list)
1301 foreach (@unprocessed)
1303 warn("WARNING: no data found for $_\n");
1307 if (!($output_filename && ($output_filename eq "-")))
1309 close(INFO_HANDLE);
1312 # Change back to initial directory
1313 chdir($cwd);
1318 # solve_relative_path(path, dir)
1320 # Solve relative path components of DIR which, if not absolute, resides in PATH.
1323 sub solve_relative_path($$)
1325 my $path = $_[0];
1326 my $dir = $_[1];
1327 my $volume;
1328 my $directories;
1329 my $filename;
1330 my @dirs; # holds path elements
1331 my $result;
1333 # Convert from Windows path to msys path
1334 if( $^O eq "msys" )
1336 # search for a windows drive letter at the beginning
1337 ($volume, $directories, $filename) = File::Spec::Win32->splitpath( $dir );
1338 if( $volume ne '' )
1340 my $uppercase_volume;
1341 # transform c/d\../e/f\g to Windows style c\d\..\e\f\g
1342 $dir = File::Spec::Win32->canonpath( $dir );
1343 # use Win32 module to retrieve path components
1344 # $uppercase_volume is not used any further
1345 ( $uppercase_volume, $directories, $filename ) = File::Spec::Win32->splitpath( $dir );
1346 @dirs = File::Spec::Win32->splitdir( $directories );
1348 # prepend volume, since in msys C: is always mounted to /c
1349 $volume =~ s|^([a-zA-Z]+):|/\L$1\E|;
1350 unshift( @dirs, $volume );
1352 # transform to Unix style '/' path
1353 $directories = File::Spec->catdir( @dirs );
1354 $dir = File::Spec->catpath( '', $directories, $filename );
1355 } else {
1356 # eliminate '\' path separators
1357 $dir = File::Spec->canonpath( $dir );
1361 $result = $dir;
1362 # Prepend path if not absolute
1363 if ($dir =~ /^[^\/]/)
1365 $result = "$path/$result";
1368 # Remove //
1369 $result =~ s/\/\//\//g;
1371 # Remove .
1372 $result =~ s/\/\.\//\//g;
1373 $result =~ s/\/\.$/\//g;
1375 # Remove trailing /
1376 $result =~ s/\/$//g;
1378 # Solve ..
1379 while ($result =~ s/\/[^\/]+\/\.\.\//\//)
1383 # Remove preceding ..
1384 $result =~ s/^\/\.\.\//\//g;
1386 return $result;
1391 # match_filename(gcov_filename, list)
1393 # Return a list of those entries of LIST which match the relative filename
1394 # GCOV_FILENAME.
1397 sub match_filename($@)
1399 my ($filename, @list) = @_;
1400 my ($vol, $dir, $file) = splitpath($filename);
1401 my @comp = splitdir($dir);
1402 my $comps = scalar(@comp);
1403 my $entry;
1404 my @result;
1406 entry:
1407 foreach $entry (@list) {
1408 my ($evol, $edir, $efile) = splitpath($entry);
1409 my @ecomp;
1410 my $ecomps;
1411 my $i;
1413 # Filename component must match
1414 if ($efile ne $file) {
1415 next;
1417 # Check directory components last to first for match
1418 @ecomp = splitdir($edir);
1419 $ecomps = scalar(@ecomp);
1420 if ($ecomps < $comps) {
1421 next;
1423 for ($i = 0; $i < $comps; $i++) {
1424 if ($comp[$comps - $i - 1] ne
1425 $ecomp[$ecomps - $i - 1]) {
1426 next entry;
1429 push(@result, $entry),
1432 return @result;
1436 # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref)
1438 # Try to solve ambiguous matches of mapping (gcov file) -> (source code) file
1439 # by comparing source code provided in the GCOV file with that of the files
1440 # in MATCHES. REL_FILENAME identifies the relative filename of the gcov
1441 # file.
1443 # Return the one real match or die if there is none.
1446 sub solve_ambiguous_match($$$)
1448 my $rel_name = $_[0];
1449 my $matches = $_[1];
1450 my $content = $_[2];
1451 my $filename;
1452 my $index;
1453 my $no_match;
1454 local *SOURCE;
1456 # Check the list of matches
1457 foreach $filename (@$matches)
1460 # Compare file contents
1461 open(SOURCE, "<", $filename)
1462 or die("ERROR: cannot read $filename!\n");
1464 $no_match = 0;
1465 for ($index = 2; <SOURCE>; $index += 3)
1467 chomp;
1469 # Also remove CR from line-end
1470 s/\015$//;
1472 if ($_ ne @$content[$index])
1474 $no_match = 1;
1475 last;
1479 close(SOURCE);
1481 if (!$no_match)
1483 info("Solved source file ambiguity for $rel_name\n");
1484 return $filename;
1488 die("ERROR: could not match gcov data for $rel_name!\n");
1493 # split_filename(filename)
1495 # Return (path, filename, extension) for a given FILENAME.
1498 sub split_filename($)
1500 my @path_components = split('/', $_[0]);
1501 my @file_components = split('\.', pop(@path_components));
1502 my $extension = pop(@file_components);
1504 return (join("/",@path_components), join(".",@file_components),
1505 $extension);
1510 # read_gcov_header(gcov_filename)
1512 # Parse file GCOV_FILENAME and return a list containing the following
1513 # information:
1515 # (source, object)
1517 # where:
1519 # source: complete relative path of the source code file (gcc >= 3.3 only)
1520 # object: name of associated graph file
1522 # Die on error.
1525 sub read_gcov_header($)
1527 my $source;
1528 my $object;
1529 local *INPUT;
1531 if (!open(INPUT, "<", $_[0]))
1533 if ($ignore_errors[$ERROR_GCOV])
1535 warn("WARNING: cannot read $_[0]!\n");
1536 return (undef,undef);
1538 die("ERROR: cannot read $_[0]!\n");
1541 while (<INPUT>)
1543 chomp($_);
1545 # Also remove CR from line-end
1546 s/\015$//;
1548 if (/^\s+-:\s+0:Source:(.*)$/)
1550 # Source: header entry
1551 $source = $1;
1553 elsif (/^\s+-:\s+0:Object:(.*)$/)
1555 # Object: header entry
1556 $object = $1;
1558 else
1560 last;
1564 close(INPUT);
1566 return ($source, $object);
1571 # br_gvec_len(vector)
1573 # Return the number of entries in the branch coverage vector.
1576 sub br_gvec_len($)
1578 my ($vec) = @_;
1580 return 0 if (!defined($vec));
1581 return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
1586 # br_gvec_get(vector, number)
1588 # Return an entry from the branch coverage vector.
1591 sub br_gvec_get($$)
1593 my ($vec, $num) = @_;
1594 my $line;
1595 my $block;
1596 my $branch;
1597 my $taken;
1598 my $offset = $num * $BR_VEC_ENTRIES;
1600 # Retrieve data from vector
1601 $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH);
1602 $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
1603 $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
1604 $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
1606 # Decode taken value from an integer
1607 if ($taken == 0) {
1608 $taken = "-";
1609 } else {
1610 $taken--;
1613 return ($line, $block, $branch, $taken);
1618 # br_gvec_push(vector, line, block, branch, taken)
1620 # Add an entry to the branch coverage vector.
1623 sub br_gvec_push($$$$$)
1625 my ($vec, $line, $block, $branch, $taken) = @_;
1626 my $offset;
1628 $vec = "" if (!defined($vec));
1629 $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES;
1631 # Encode taken value into an integer
1632 if ($taken eq "-") {
1633 $taken = 0;
1634 } else {
1635 $taken++;
1638 # Add to vector
1639 vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line;
1640 vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
1641 vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
1642 vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
1644 return $vec;
1649 # read_gcov_file(gcov_filename)
1651 # Parse file GCOV_FILENAME (.gcov file format) and return the list:
1652 # (reference to gcov_content, reference to gcov_branch, reference to gcov_func)
1654 # gcov_content is a list of 3 elements
1655 # (flag, count, source) for each source code line:
1657 # $result[($line_number-1)*3+0] = instrumentation flag for line $line_number
1658 # $result[($line_number-1)*3+1] = execution count for line $line_number
1659 # $result[($line_number-1)*3+2] = source code text for line $line_number
1661 # gcov_branch is a vector of 4 4-byte long elements for each branch:
1662 # line number, block number, branch number, count + 1 or 0
1664 # gcov_func is a list of 2 elements
1665 # (number of calls, function name) for each function
1667 # Die on error.
1670 sub read_gcov_file($)
1672 my $filename = $_[0];
1673 my @result = ();
1674 my $branches = "";
1675 my @functions = ();
1676 my $number;
1677 my $exclude_flag = 0;
1678 my $exclude_line = 0;
1679 my $last_block = $UNNAMED_BLOCK;
1680 my $last_line = 0;
1681 local *INPUT;
1683 if (!open(INPUT, "<", $filename)) {
1684 if ($ignore_errors[$ERROR_GCOV])
1686 warn("WARNING: cannot read $filename!\n");
1687 return (undef, undef, undef);
1689 die("ERROR: cannot read $filename!\n");
1692 if ($gcov_version < $GCOV_VERSION_3_3_0)
1694 # Expect gcov format as used in gcc < 3.3
1695 while (<INPUT>)
1697 chomp($_);
1699 # Also remove CR from line-end
1700 s/\015$//;
1702 if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) {
1703 next if (!$br_coverage);
1704 next if ($exclude_line);
1705 $branches = br_gvec_push($branches, $last_line,
1706 $last_block, $1, $2);
1707 } elsif (/^branch\s+(\d+)\s+never\s+executed/) {
1708 next if (!$br_coverage);
1709 next if ($exclude_line);
1710 $branches = br_gvec_push($branches, $last_line,
1711 $last_block, $1, '-');
1713 elsif (/^call/ || /^function/)
1715 # Function call return data
1717 else
1719 $last_line++;
1720 # Check for exclusion markers
1721 if (!$no_markers) {
1722 if (/$EXCL_STOP/) {
1723 $exclude_flag = 0;
1724 } elsif (/$EXCL_START/) {
1725 $exclude_flag = 1;
1727 if (/$EXCL_LINE/ || $exclude_flag) {
1728 $exclude_line = 1;
1729 } else {
1730 $exclude_line = 0;
1733 # Source code execution data
1734 if (/^\t\t(.*)$/)
1736 # Uninstrumented line
1737 push(@result, 0);
1738 push(@result, 0);
1739 push(@result, $1);
1740 next;
1742 $number = (split(" ",substr($_, 0, 16)))[0];
1744 # Check for zero count which is indicated
1745 # by ######
1746 if ($number eq "######") { $number = 0; }
1748 if ($exclude_line) {
1749 # Register uninstrumented line instead
1750 push(@result, 0);
1751 push(@result, 0);
1752 } else {
1753 push(@result, 1);
1754 push(@result, $number);
1756 push(@result, substr($_, 16));
1760 else
1762 # Expect gcov format as used in gcc >= 3.3
1763 while (<INPUT>)
1765 chomp($_);
1767 # Also remove CR from line-end
1768 s/\015$//;
1770 if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) {
1771 # Block information - used to group related
1772 # branches
1773 $last_line = $2;
1774 $last_block = $3;
1775 } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) {
1776 next if (!$br_coverage);
1777 next if ($exclude_line);
1778 $branches = br_gvec_push($branches, $last_line,
1779 $last_block, $1, $2);
1780 } elsif (/^branch\s+(\d+)\s+never\s+executed/) {
1781 next if (!$br_coverage);
1782 next if ($exclude_line);
1783 $branches = br_gvec_push($branches, $last_line,
1784 $last_block, $1, '-');
1786 elsif (/^function\s+(.+)\s+called\s+(\d+)\s+/)
1788 next if (!$func_coverage);
1789 if ($exclude_line) {
1790 next;
1792 push(@functions, $2, $1);
1794 elsif (/^call/)
1796 # Function call return data
1798 elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/)
1800 my ($count, $line, $code) = ($1, $2, $3);
1802 $last_line = $line;
1803 $last_block = $UNNAMED_BLOCK;
1804 # Check for exclusion markers
1805 if (!$no_markers) {
1806 if (/$EXCL_STOP/) {
1807 $exclude_flag = 0;
1808 } elsif (/$EXCL_START/) {
1809 $exclude_flag = 1;
1811 if (/$EXCL_LINE/ || $exclude_flag) {
1812 $exclude_line = 1;
1813 } else {
1814 $exclude_line = 0;
1817 # <exec count>:<line number>:<source code>
1818 if ($line eq "0")
1820 # Extra data
1822 elsif ($count eq "-")
1824 # Uninstrumented line
1825 push(@result, 0);
1826 push(@result, 0);
1827 push(@result, $code);
1829 else
1831 if ($exclude_line) {
1832 push(@result, 0);
1833 push(@result, 0);
1834 } else {
1835 # Check for zero count
1836 if ($count eq "#####") {
1837 $count = 0;
1839 push(@result, 1);
1840 push(@result, $count);
1842 push(@result, $code);
1848 close(INPUT);
1849 if ($exclude_flag) {
1850 warn("WARNING: unterminated exclusion section in $filename\n");
1852 return(\@result, $branches, \@functions);
1857 # Get the GCOV tool version. Return an integer number which represents the
1858 # GCOV version. Version numbers can be compared using standard integer
1859 # operations.
1862 sub get_gcov_version()
1864 local *HANDLE;
1865 my $version_string;
1866 my $result;
1868 open(GCOV_PIPE, "-|", "$gcov_tool -v")
1869 or die("ERROR: cannot retrieve gcov version!\n");
1870 $version_string = <GCOV_PIPE>;
1871 close(GCOV_PIPE);
1873 $result = 0;
1874 if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/)
1876 if (defined($4))
1878 info("Found gcov version: $1.$2.$4\n");
1879 $result = $1 << 16 | $2 << 8 | $4;
1881 else
1883 info("Found gcov version: $1.$2\n");
1884 $result = $1 << 16 | $2 << 8;
1887 return ($result, $version_string);
1892 # info(printf_parameter)
1894 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
1895 # is not set.
1898 sub info(@)
1900 if (!$quiet)
1902 # Print info string
1903 if (defined($output_filename) && ($output_filename eq "-"))
1905 # Don't interfere with the .info output to STDOUT
1906 printf(STDERR @_);
1908 else
1910 printf(@_);
1917 # int_handler()
1919 # Called when the script was interrupted by an INT signal (e.g. CTRl-C)
1922 sub int_handler()
1924 if ($cwd) { chdir($cwd); }
1925 info("Aborted.\n");
1926 exit(1);
1931 # system_no_output(mode, parameters)
1933 # Call an external program using PARAMETERS while suppressing depending on
1934 # the value of MODE:
1936 # MODE & 1: suppress STDOUT
1937 # MODE & 2: suppress STDERR
1939 # Return 0 on success, non-zero otherwise.
1942 sub system_no_output($@)
1944 my $mode = shift;
1945 my $result;
1946 local *OLD_STDERR;
1947 local *OLD_STDOUT;
1949 # Save old stdout and stderr handles
1950 ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT");
1951 ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR");
1953 # Redirect to /dev/null
1954 ($mode & 1) && open(STDOUT, ">", "/dev/null");
1955 ($mode & 2) && open(STDERR, ">", "/dev/null");
1957 debug("system(".join(' ', @_).")\n");
1958 system(@_);
1959 $result = $?;
1961 # Close redirected handles
1962 ($mode & 1) && close(STDOUT);
1963 ($mode & 2) && close(STDERR);
1965 # Restore old handles
1966 ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT");
1967 ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR");
1969 return $result;
1974 # read_config(filename)
1976 # Read configuration file FILENAME and return a reference to a hash containing
1977 # all valid key=value pairs found.
1980 sub read_config($)
1982 my $filename = $_[0];
1983 my %result;
1984 my $key;
1985 my $value;
1986 local *HANDLE;
1988 if (!open(HANDLE, "<", $filename))
1990 warn("WARNING: cannot read configuration file $filename\n");
1991 return undef;
1993 while (<HANDLE>)
1995 chomp;
1996 # Skip comments
1997 s/#.*//;
1998 # Remove leading blanks
1999 s/^\s+//;
2000 # Remove trailing blanks
2001 s/\s+$//;
2002 next unless length;
2003 ($key, $value) = split(/\s*=\s*/, $_, 2);
2004 if (defined($key) && defined($value))
2006 $result{$key} = $value;
2008 else
2010 warn("WARNING: malformed statement in line $. ".
2011 "of configuration file $filename\n");
2014 close(HANDLE);
2015 return \%result;
2020 # apply_config(REF)
2022 # REF is a reference to a hash containing the following mapping:
2024 # key_string => var_ref
2026 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
2027 # variable. If the global configuration hashes CONFIG or OPT_RC contain a value
2028 # for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
2031 sub apply_config($)
2033 my $ref = $_[0];
2035 foreach (keys(%{$ref}))
2037 if (defined($opt_rc{$_})) {
2038 ${$ref->{$_}} = $opt_rc{$_};
2039 } elsif (defined($config->{$_})) {
2040 ${$ref->{$_}} = $config->{$_};
2047 # get_exclusion_data(filename)
2049 # Scan specified source code file for exclusion markers and return
2050 # linenumber -> 1
2051 # for all lines which should be excluded.
2054 sub get_exclusion_data($)
2056 my ($filename) = @_;
2057 my %list;
2058 my $flag = 0;
2059 local *HANDLE;
2061 if (!open(HANDLE, "<", $filename)) {
2062 warn("WARNING: could not open $filename\n");
2063 return undef;
2065 while (<HANDLE>) {
2066 if (/$EXCL_STOP/) {
2067 $flag = 0;
2068 } elsif (/$EXCL_START/) {
2069 $flag = 1;
2071 if (/$EXCL_LINE/ || $flag) {
2072 $list{$.} = 1;
2075 close(HANDLE);
2077 if ($flag) {
2078 warn("WARNING: unterminated exclusion section in $filename\n");
2081 return \%list;
2086 # apply_exclusion_data(instr, graph)
2088 # Remove lines from instr and graph data structures which are marked
2089 # for exclusion in the source code file.
2091 # Return adjusted (instr, graph).
2093 # graph : file name -> function data
2094 # function data : function name -> line data
2095 # line data : [ line1, line2, ... ]
2097 # instr : filename -> line data
2098 # line data : [ line1, line2, ... ]
2101 sub apply_exclusion_data($$)
2103 my ($instr, $graph) = @_;
2104 my $filename;
2105 my %excl_data;
2106 my $excl_read_failed = 0;
2108 # Collect exclusion marker data
2109 foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) {
2110 my $excl = get_exclusion_data($filename);
2112 # Skip and note if file could not be read
2113 if (!defined($excl)) {
2114 $excl_read_failed = 1;
2115 next;
2118 # Add to collection if there are markers
2119 $excl_data{$filename} = $excl if (keys(%{$excl}) > 0);
2122 # Warn if not all source files could be read
2123 if ($excl_read_failed) {
2124 warn("WARNING: some exclusion markers may be ignored\n");
2127 # Skip if no markers were found
2128 return ($instr, $graph) if (keys(%excl_data) == 0);
2130 # Apply exclusion marker data to graph
2131 foreach $filename (keys(%excl_data)) {
2132 my $function_data = $graph->{$filename};
2133 my $excl = $excl_data{$filename};
2134 my $function;
2136 next if (!defined($function_data));
2138 foreach $function (keys(%{$function_data})) {
2139 my $line_data = $function_data->{$function};
2140 my $line;
2141 my @new_data;
2143 # To be consistent with exclusion parser in non-initial
2144 # case we need to remove a function if the first line
2145 # was excluded
2146 if ($excl->{$line_data->[0]}) {
2147 delete($function_data->{$function});
2148 next;
2150 # Copy only lines which are not excluded
2151 foreach $line (@{$line_data}) {
2152 push(@new_data, $line) if (!$excl->{$line});
2155 # Store modified list
2156 if (scalar(@new_data) > 0) {
2157 $function_data->{$function} = \@new_data;
2158 } else {
2159 # All of this function was excluded
2160 delete($function_data->{$function});
2164 # Check if all functions of this file were excluded
2165 if (keys(%{$function_data}) == 0) {
2166 delete($graph->{$filename});
2170 # Apply exclusion marker data to instr
2171 foreach $filename (keys(%excl_data)) {
2172 my $line_data = $instr->{$filename};
2173 my $excl = $excl_data{$filename};
2174 my $line;
2175 my @new_data;
2177 next if (!defined($line_data));
2179 # Copy only lines which are not excluded
2180 foreach $line (@{$line_data}) {
2181 push(@new_data, $line) if (!$excl->{$line});
2184 # Store modified list
2185 $instr->{$filename} = \@new_data;
2188 return ($instr, $graph);
2192 sub process_graphfile($$)
2194 my ($file, $dir) = @_;
2195 my $graph_filename = $file;
2196 my $graph_dir;
2197 my $graph_basename;
2198 my $source_dir;
2199 my $base_dir;
2200 my $graph;
2201 my $instr;
2202 my $filename;
2203 local *INFO_HANDLE;
2205 info("Processing %s\n", abs2rel($file, $dir));
2207 # Get path to data file in absolute and normalized form (begins with /,
2208 # contains no more ../ or ./)
2209 $graph_filename = solve_relative_path($cwd, $graph_filename);
2211 # Get directory and basename of data file
2212 ($graph_dir, $graph_basename) = split_filename($graph_filename);
2214 $source_dir = $graph_dir;
2215 if (is_compat($COMPAT_MODE_LIBTOOL)) {
2216 # Avoid files from .libs dirs
2217 $source_dir =~ s/\.libs$//;
2220 # Construct base_dir for current file
2221 if ($base_directory)
2223 $base_dir = $base_directory;
2225 else
2227 $base_dir = $source_dir;
2230 if ($gcov_version < $GCOV_VERSION_3_4_0)
2232 if (is_compat($COMPAT_MODE_HAMMER))
2234 ($instr, $graph) = read_bbg($graph_filename);
2236 else
2238 ($instr, $graph) = read_bb($graph_filename);
2241 else
2243 ($instr, $graph) = read_gcno($graph_filename);
2246 # Try to find base directory automatically if requested by user
2247 if ($rc_auto_base) {
2248 $base_dir = find_base_from_graph($base_dir, $instr, $graph);
2251 ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph);
2253 if (!$no_markers) {
2254 # Apply exclusion marker data to graph file data
2255 ($instr, $graph) = apply_exclusion_data($instr, $graph);
2258 # Check whether we're writing to a single file
2259 if ($output_filename)
2261 if ($output_filename eq "-")
2263 *INFO_HANDLE = *STDOUT;
2265 else
2267 # Append to output file
2268 open(INFO_HANDLE, ">>", $output_filename)
2269 or die("ERROR: cannot write to ".
2270 "$output_filename!\n");
2273 else
2275 # Open .info file for output
2276 open(INFO_HANDLE, ">", "$graph_filename.info")
2277 or die("ERROR: cannot create $graph_filename.info!\n");
2280 # Write test name
2281 printf(INFO_HANDLE "TN:%s\n", $test_name);
2282 foreach $filename (sort(keys(%{$instr})))
2284 my $funcdata = $graph->{$filename};
2285 my $line;
2286 my $linedata;
2288 print(INFO_HANDLE "SF:$filename\n");
2290 if (defined($funcdata) && $func_coverage) {
2291 my @functions = sort {$funcdata->{$a}->[0] <=>
2292 $funcdata->{$b}->[0]}
2293 keys(%{$funcdata});
2294 my $func;
2296 # Gather list of instrumented lines and functions
2297 foreach $func (@functions) {
2298 $linedata = $funcdata->{$func};
2300 # Print function name and starting line
2301 print(INFO_HANDLE "FN:".$linedata->[0].
2302 ",".filter_fn_name($func)."\n");
2304 # Print zero function coverage data
2305 foreach $func (@functions) {
2306 print(INFO_HANDLE "FNDA:0,".
2307 filter_fn_name($func)."\n");
2309 # Print function summary
2310 print(INFO_HANDLE "FNF:".scalar(@functions)."\n");
2311 print(INFO_HANDLE "FNH:0\n");
2313 # Print zero line coverage data
2314 foreach $line (@{$instr->{$filename}}) {
2315 print(INFO_HANDLE "DA:$line,0\n");
2317 # Print line summary
2318 print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n");
2319 print(INFO_HANDLE "LH:0\n");
2321 print(INFO_HANDLE "end_of_record\n");
2323 if (!($output_filename && ($output_filename eq "-")))
2325 close(INFO_HANDLE);
2329 sub filter_fn_name($)
2331 my ($fn) = @_;
2333 # Remove characters used internally as function name delimiters
2334 $fn =~ s/[,=]/_/g;
2336 return $fn;
2339 sub warn_handler($)
2341 my ($msg) = @_;
2343 warn("$tool_name: $msg");
2346 sub die_handler($)
2348 my ($msg) = @_;
2350 die("$tool_name: $msg");
2355 # graph_error(filename, message)
2357 # Print message about error in graph file. If ignore_graph_error is set, return.
2358 # Otherwise abort.
2361 sub graph_error($$)
2363 my ($filename, $msg) = @_;
2365 if ($ignore[$ERROR_GRAPH]) {
2366 warn("WARNING: $filename: $msg - skipping\n");
2367 return;
2369 die("ERROR: $filename: $msg\n");
2373 # graph_expect(description)
2375 # If debug is set to a non-zero value, print the specified description of what
2376 # is expected to be read next from the graph file.
2379 sub graph_expect($)
2381 my ($msg) = @_;
2383 if (!$debug || !defined($msg)) {
2384 return;
2387 print(STDERR "DEBUG: expecting $msg\n");
2391 # graph_read(handle, bytes[, description, peek])
2393 # Read and return the specified number of bytes from handle. Return undef
2394 # if the number of bytes could not be read. If PEEK is non-zero, reset
2395 # file position after read.
2398 sub graph_read(*$;$$)
2400 my ($handle, $length, $desc, $peek) = @_;
2401 my $data;
2402 my $result;
2403 my $pos;
2405 graph_expect($desc);
2406 if ($peek) {
2407 $pos = tell($handle);
2408 if ($pos == -1) {
2409 warn("Could not get current file position: $!\n");
2410 return undef;
2413 $result = read($handle, $data, $length);
2414 if ($debug) {
2415 my $op = $peek ? "peek" : "read";
2416 my $ascii = "";
2417 my $hex = "";
2418 my $i;
2420 print(STDERR "DEBUG: $op($length)=$result: ");
2421 for ($i = 0; $i < length($data); $i++) {
2422 my $c = substr($data, $i, 1);;
2423 my $n = ord($c);
2425 $hex .= sprintf("%02x ", $n);
2426 if ($n >= 32 && $n <= 127) {
2427 $ascii .= $c;
2428 } else {
2429 $ascii .= ".";
2432 print(STDERR "$hex |$ascii|");
2433 print(STDERR "\n");
2435 if ($peek) {
2436 if (!seek($handle, $pos, 0)) {
2437 warn("Could not set file position: $!\n");
2438 return undef;
2441 if ($result != $length) {
2442 return undef;
2444 return $data;
2448 # graph_skip(handle, bytes[, description])
2450 # Read and discard the specified number of bytes from handle. Return non-zero
2451 # if bytes could be read, zero otherwise.
2454 sub graph_skip(*$;$)
2456 my ($handle, $length, $desc) = @_;
2458 if (defined(graph_read($handle, $length, $desc))) {
2459 return 1;
2461 return 0;
2465 # sort_uniq(list)
2467 # Return list in numerically ascending order and without duplicate entries.
2470 sub sort_uniq(@)
2472 my (@list) = @_;
2473 my %hash;
2475 foreach (@list) {
2476 $hash{$_} = 1;
2478 return sort { $a <=> $b } keys(%hash);
2482 # sort_uniq_lex(list)
2484 # Return list in lexically ascending order and without duplicate entries.
2487 sub sort_uniq_lex(@)
2489 my (@list) = @_;
2490 my %hash;
2492 foreach (@list) {
2493 $hash{$_} = 1;
2495 return sort keys(%hash);
2499 # parent_dir(dir)
2501 # Return parent directory for DIR. DIR must not contain relative path
2502 # components.
2505 sub parent_dir($)
2507 my ($dir) = @_;
2508 my ($v, $d, $f) = splitpath($dir, 1);
2509 my @dirs = splitdir($d);
2511 pop(@dirs);
2513 return catpath($v, catdir(@dirs), $f);
2517 # find_base_from_graph(base_dir, instr, graph)
2519 # Try to determine the base directory of the graph file specified by INSTR
2520 # and GRAPH. The base directory is the base for all relative filenames in
2521 # the graph file. It is defined by the current working directory at time
2522 # of compiling the source file.
2524 # This function implements a heuristic which relies on the following
2525 # assumptions:
2526 # - all files used for compilation are still present at their location
2527 # - the base directory is either BASE_DIR or one of its parent directories
2528 # - files by the same name are not present in multiple parent directories
2531 sub find_base_from_graph($$$)
2533 my ($base_dir, $instr, $graph) = @_;
2534 my $old_base;
2535 my $best_miss;
2536 my $best_base;
2537 my %rel_files;
2539 # Determine list of relative paths
2540 foreach my $filename (keys(%{$instr}), keys(%{$graph})) {
2541 next if (file_name_is_absolute($filename));
2543 $rel_files{$filename} = 1;
2546 # Early exit if there are no relative paths
2547 return $base_dir if (!%rel_files);
2549 do {
2550 my $miss = 0;
2552 foreach my $filename (keys(%rel_files)) {
2553 if (!-e solve_relative_path($base_dir, $filename)) {
2554 $miss++;
2558 debug("base_dir=$base_dir miss=$miss\n");
2560 # Exit if we find an exact match with no misses
2561 return $base_dir if ($miss == 0);
2563 # No exact match, aim for the one with the least source file
2564 # misses
2565 if (!defined($best_base) || $miss < $best_miss) {
2566 $best_base = $base_dir;
2567 $best_miss = $miss;
2570 # Repeat until there's no more parent directory
2571 $old_base = $base_dir;
2572 $base_dir = parent_dir($base_dir);
2573 } while ($old_base ne $base_dir);
2575 return $best_base;
2579 # adjust_graph_filenames(base_dir, instr, graph)
2581 # Make relative paths in INSTR and GRAPH absolute and apply
2582 # geninfo_adjust_src_path setting to graph file data.
2585 sub adjust_graph_filenames($$$)
2587 my ($base_dir, $instr, $graph) = @_;
2589 foreach my $filename (keys(%{$instr})) {
2590 my $old_filename = $filename;
2592 # Convert to absolute canonical form
2593 $filename = solve_relative_path($base_dir, $filename);
2595 # Apply adjustment
2596 if (defined($adjust_src_pattern)) {
2597 $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g;
2600 if ($filename ne $old_filename) {
2601 $instr->{$filename} = delete($instr->{$old_filename});
2605 foreach my $filename (keys(%{$graph})) {
2606 my $old_filename = $filename;
2608 # Make absolute
2609 # Convert to absolute canonical form
2610 $filename = solve_relative_path($base_dir, $filename);
2612 # Apply adjustment
2613 if (defined($adjust_src_pattern)) {
2614 $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g;
2617 if ($filename ne $old_filename) {
2618 $graph->{$filename} = delete($graph->{$old_filename});
2622 return ($instr, $graph);
2626 # graph_cleanup(graph)
2628 # Remove entries for functions with no lines. Remove duplicate line numbers.
2629 # Sort list of line numbers numerically ascending.
2632 sub graph_cleanup($)
2634 my ($graph) = @_;
2635 my $filename;
2637 foreach $filename (keys(%{$graph})) {
2638 my $per_file = $graph->{$filename};
2639 my $function;
2641 foreach $function (keys(%{$per_file})) {
2642 my $lines = $per_file->{$function};
2644 if (scalar(@$lines) == 0) {
2645 # Remove empty function
2646 delete($per_file->{$function});
2647 next;
2649 # Normalize list
2650 $per_file->{$function} = [ sort_uniq(@$lines) ];
2652 if (scalar(keys(%{$per_file})) == 0) {
2653 # Remove empty file
2654 delete($graph->{$filename});
2660 # graph_find_base(bb)
2662 # Try to identify the filename which is the base source file for the
2663 # specified bb data.
2666 sub graph_find_base($)
2668 my ($bb) = @_;
2669 my %file_count;
2670 my $basefile;
2671 my $file;
2672 my $func;
2673 my $filedata;
2674 my $count;
2675 my $num;
2677 # Identify base name for this bb data.
2678 foreach $func (keys(%{$bb})) {
2679 $filedata = $bb->{$func};
2681 foreach $file (keys(%{$filedata})) {
2682 $count = $file_count{$file};
2684 # Count file occurrence
2685 $file_count{$file} = defined($count) ? $count + 1 : 1;
2688 $count = 0;
2689 $num = 0;
2690 foreach $file (keys(%file_count)) {
2691 if ($file_count{$file} > $count) {
2692 # The file that contains code for the most functions
2693 # is likely the base file
2694 $count = $file_count{$file};
2695 $num = 1;
2696 $basefile = $file;
2697 } elsif ($file_count{$file} == $count) {
2698 # If more than one file could be the basefile, we
2699 # don't have a basefile
2700 $basefile = undef;
2704 return $basefile;
2708 # graph_from_bb(bb, fileorder, bb_filename)
2710 # Convert data from bb to the graph format and list of instrumented lines.
2711 # Returns (instr, graph).
2713 # bb : function name -> file data
2714 # : undef -> file order
2715 # file data : filename -> line data
2716 # line data : [ line1, line2, ... ]
2718 # file order : function name -> [ filename1, filename2, ... ]
2720 # graph : file name -> function data
2721 # function data : function name -> line data
2722 # line data : [ line1, line2, ... ]
2724 # instr : filename -> line data
2725 # line data : [ line1, line2, ... ]
2728 sub graph_from_bb($$$)
2730 my ($bb, $fileorder, $bb_filename) = @_;
2731 my $graph = {};
2732 my $instr = {};
2733 my $basefile;
2734 my $file;
2735 my $func;
2736 my $filedata;
2737 my $linedata;
2738 my $order;
2740 $basefile = graph_find_base($bb);
2741 # Create graph structure
2742 foreach $func (keys(%{$bb})) {
2743 $filedata = $bb->{$func};
2744 $order = $fileorder->{$func};
2746 # Account for lines in functions
2747 if (defined($basefile) && defined($filedata->{$basefile})) {
2748 # If the basefile contributes to this function,
2749 # account this function to the basefile.
2750 $graph->{$basefile}->{$func} = $filedata->{$basefile};
2751 } else {
2752 # If the basefile does not contribute to this function,
2753 # account this function to the first file contributing
2754 # lines.
2755 $graph->{$order->[0]}->{$func} =
2756 $filedata->{$order->[0]};
2759 foreach $file (keys(%{$filedata})) {
2760 # Account for instrumented lines
2761 $linedata = $filedata->{$file};
2762 push(@{$instr->{$file}}, @$linedata);
2765 # Clean up array of instrumented lines
2766 foreach $file (keys(%{$instr})) {
2767 $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ];
2770 return ($instr, $graph);
2774 # graph_add_order(fileorder, function, filename)
2776 # Add an entry for filename to the fileorder data set for function.
2779 sub graph_add_order($$$)
2781 my ($fileorder, $function, $filename) = @_;
2782 my $item;
2783 my $list;
2785 $list = $fileorder->{$function};
2786 foreach $item (@$list) {
2787 if ($item eq $filename) {
2788 return;
2791 push(@$list, $filename);
2792 $fileorder->{$function} = $list;
2796 # read_bb_word(handle[, description])
2798 # Read and return a word in .bb format from handle.
2801 sub read_bb_word(*;$)
2803 my ($handle, $desc) = @_;
2805 return graph_read($handle, 4, $desc);
2809 # read_bb_value(handle[, description])
2811 # Read a word in .bb format from handle and return the word and its integer
2812 # value.
2815 sub read_bb_value(*;$)
2817 my ($handle, $desc) = @_;
2818 my $word;
2820 $word = read_bb_word($handle, $desc);
2821 return undef if (!defined($word));
2823 return ($word, unpack("V", $word));
2827 # read_bb_string(handle, delimiter)
2829 # Read and return a string in .bb format from handle up to the specified
2830 # delimiter value.
2833 sub read_bb_string(*$)
2835 my ($handle, $delimiter) = @_;
2836 my $word;
2837 my $value;
2838 my $string = "";
2840 graph_expect("string");
2841 do {
2842 ($word, $value) = read_bb_value($handle, "string or delimiter");
2843 return undef if (!defined($value));
2844 if ($value != $delimiter) {
2845 $string .= $word;
2847 } while ($value != $delimiter);
2848 $string =~ s/\0//g;
2850 return $string;
2854 # read_bb(filename)
2856 # Read the contents of the specified .bb file and return (instr, graph), where:
2858 # instr : filename -> line data
2859 # line data : [ line1, line2, ... ]
2861 # graph : filename -> file_data
2862 # file_data : function name -> line_data
2863 # line_data : [ line1, line2, ... ]
2865 # See the gcov info pages of gcc 2.95 for a description of the .bb file format.
2868 sub read_bb($)
2870 my ($bb_filename) = @_;
2871 my $minus_one = 0x80000001;
2872 my $minus_two = 0x80000002;
2873 my $value;
2874 my $filename;
2875 my $function;
2876 my $bb = {};
2877 my $fileorder = {};
2878 my $instr;
2879 my $graph;
2880 local *HANDLE;
2882 open(HANDLE, "<", $bb_filename) or goto open_error;
2883 binmode(HANDLE);
2884 while (!eof(HANDLE)) {
2885 $value = read_bb_value(*HANDLE, "data word");
2886 goto incomplete if (!defined($value));
2887 if ($value == $minus_one) {
2888 # Source file name
2889 graph_expect("filename");
2890 $filename = read_bb_string(*HANDLE, $minus_one);
2891 goto incomplete if (!defined($filename));
2892 } elsif ($value == $minus_two) {
2893 # Function name
2894 graph_expect("function name");
2895 $function = read_bb_string(*HANDLE, $minus_two);
2896 goto incomplete if (!defined($function));
2897 } elsif ($value > 0) {
2898 # Line number
2899 if (!defined($filename) || !defined($function)) {
2900 warn("WARNING: unassigned line number ".
2901 "$value\n");
2902 next;
2904 push(@{$bb->{$function}->{$filename}}, $value);
2905 graph_add_order($fileorder, $function, $filename);
2908 close(HANDLE);
2909 ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename);
2910 graph_cleanup($graph);
2912 return ($instr, $graph);
2914 open_error:
2915 graph_error($bb_filename, "could not open file");
2916 return undef;
2917 incomplete:
2918 graph_error($bb_filename, "reached unexpected end of file");
2919 return undef;
2923 # read_bbg_word(handle[, description])
2925 # Read and return a word in .bbg format.
2928 sub read_bbg_word(*;$)
2930 my ($handle, $desc) = @_;
2932 return graph_read($handle, 4, $desc);
2936 # read_bbg_value(handle[, description])
2938 # Read a word in .bbg format from handle and return its integer value.
2941 sub read_bbg_value(*;$)
2943 my ($handle, $desc) = @_;
2944 my $word;
2946 $word = read_bbg_word($handle, $desc);
2947 return undef if (!defined($word));
2949 return unpack("N", $word);
2953 # read_bbg_string(handle)
2955 # Read and return a string in .bbg format.
2958 sub read_bbg_string(*)
2960 my ($handle, $desc) = @_;
2961 my $length;
2962 my $string;
2964 graph_expect("string");
2965 # Read string length
2966 $length = read_bbg_value($handle, "string length");
2967 return undef if (!defined($length));
2968 if ($length == 0) {
2969 return "";
2971 # Read string
2972 $string = graph_read($handle, $length, "string");
2973 return undef if (!defined($string));
2974 # Skip padding
2975 graph_skip($handle, 4 - $length % 4, "string padding") or return undef;
2977 return $string;
2981 # read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename,
2982 # function)
2984 # Read a bbg format lines record from handle and add the relevant data to
2985 # bb and fileorder. Return filename on success, undef on error.
2988 sub read_bbg_lines_record(*$$$$$)
2990 my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_;
2991 my $string;
2992 my $lineno;
2994 graph_expect("lines record");
2995 # Skip basic block index
2996 graph_skip($handle, 4, "basic block index") or return undef;
2997 while (1) {
2998 # Read line number
2999 $lineno = read_bbg_value($handle, "line number");
3000 return undef if (!defined($lineno));
3001 if ($lineno == 0) {
3002 # Got a marker for a new filename
3003 graph_expect("filename");
3004 $string = read_bbg_string($handle);
3005 return undef if (!defined($string));
3006 # Check for end of record
3007 if ($string eq "") {
3008 return $filename;
3010 $filename = $string;
3011 if (!exists($bb->{$function}->{$filename})) {
3012 $bb->{$function}->{$filename} = [];
3014 next;
3016 # Got an actual line number
3017 if (!defined($filename)) {
3018 warn("WARNING: unassigned line number in ".
3019 "$bbg_filename\n");
3020 next;
3022 push(@{$bb->{$function}->{$filename}}, $lineno);
3023 graph_add_order($fileorder, $function, $filename);
3028 # read_bbg(filename)
3030 # Read the contents of the specified .bbg file and return the following mapping:
3031 # graph: filename -> file_data
3032 # file_data: function name -> line_data
3033 # line_data: [ line1, line2, ... ]
3035 # See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code for a description
3036 # of the .bbg format.
3039 sub read_bbg($)
3041 my ($bbg_filename) = @_;
3042 my $file_magic = 0x67626267;
3043 my $tag_function = 0x01000000;
3044 my $tag_lines = 0x01450000;
3045 my $word;
3046 my $tag;
3047 my $length;
3048 my $function;
3049 my $filename;
3050 my $bb = {};
3051 my $fileorder = {};
3052 my $instr;
3053 my $graph;
3054 local *HANDLE;
3056 open(HANDLE, "<", $bbg_filename) or goto open_error;
3057 binmode(HANDLE);
3058 # Read magic
3059 $word = read_bbg_value(*HANDLE, "file magic");
3060 goto incomplete if (!defined($word));
3061 # Check magic
3062 if ($word != $file_magic) {
3063 goto magic_error;
3065 # Skip version
3066 graph_skip(*HANDLE, 4, "version") or goto incomplete;
3067 while (!eof(HANDLE)) {
3068 # Read record tag
3069 $tag = read_bbg_value(*HANDLE, "record tag");
3070 goto incomplete if (!defined($tag));
3071 # Read record length
3072 $length = read_bbg_value(*HANDLE, "record length");
3073 goto incomplete if (!defined($tag));
3074 if ($tag == $tag_function) {
3075 graph_expect("function record");
3076 # Read function name
3077 graph_expect("function name");
3078 $function = read_bbg_string(*HANDLE);
3079 goto incomplete if (!defined($function));
3080 $filename = undef;
3081 # Skip function checksum
3082 graph_skip(*HANDLE, 4, "function checksum")
3083 or goto incomplete;
3084 } elsif ($tag == $tag_lines) {
3085 # Read lines record
3086 $filename = read_bbg_lines_record(HANDLE, $bbg_filename,
3087 $bb, $fileorder, $filename,
3088 $function);
3089 goto incomplete if (!defined($filename));
3090 } else {
3091 # Skip record contents
3092 graph_skip(*HANDLE, $length, "unhandled record")
3093 or goto incomplete;
3096 close(HANDLE);
3097 ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename);
3098 graph_cleanup($graph);
3100 return ($instr, $graph);
3102 open_error:
3103 graph_error($bbg_filename, "could not open file");
3104 return undef;
3105 incomplete:
3106 graph_error($bbg_filename, "reached unexpected end of file");
3107 return undef;
3108 magic_error:
3109 graph_error($bbg_filename, "found unrecognized bbg file magic");
3110 return undef;
3114 # read_gcno_word(handle[, description, peek])
3116 # Read and return a word in .gcno format.
3119 sub read_gcno_word(*;$$)
3121 my ($handle, $desc, $peek) = @_;
3123 return graph_read($handle, 4, $desc, $peek);
3127 # read_gcno_value(handle, big_endian[, description, peek])
3129 # Read a word in .gcno format from handle and return its integer value
3130 # according to the specified endianness. If PEEK is non-zero, reset file
3131 # position after read.
3134 sub read_gcno_value(*$;$$)
3136 my ($handle, $big_endian, $desc, $peek) = @_;
3137 my $word;
3138 my $pos;
3140 $word = read_gcno_word($handle, $desc, $peek);
3141 return undef if (!defined($word));
3142 if ($big_endian) {
3143 return unpack("N", $word);
3144 } else {
3145 return unpack("V", $word);
3150 # read_gcno_string(handle, big_endian)
3152 # Read and return a string in .gcno format.
3155 sub read_gcno_string(*$)
3157 my ($handle, $big_endian) = @_;
3158 my $length;
3159 my $string;
3161 graph_expect("string");
3162 # Read string length
3163 $length = read_gcno_value($handle, $big_endian, "string length");
3164 return undef if (!defined($length));
3165 if ($length == 0) {
3166 return "";
3168 $length *= 4;
3169 # Read string
3170 $string = graph_read($handle, $length, "string and padding");
3171 return undef if (!defined($string));
3172 $string =~ s/\0//g;
3174 return $string;
3178 # read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename,
3179 # function, big_endian)
3181 # Read a gcno format lines record from handle and add the relevant data to
3182 # bb and fileorder. Return filename on success, undef on error.
3185 sub read_gcno_lines_record(*$$$$$$)
3187 my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function,
3188 $big_endian) = @_;
3189 my $string;
3190 my $lineno;
3192 graph_expect("lines record");
3193 # Skip basic block index
3194 graph_skip($handle, 4, "basic block index") or return undef;
3195 while (1) {
3196 # Read line number
3197 $lineno = read_gcno_value($handle, $big_endian, "line number");
3198 return undef if (!defined($lineno));
3199 if ($lineno == 0) {
3200 # Got a marker for a new filename
3201 graph_expect("filename");
3202 $string = read_gcno_string($handle, $big_endian);
3203 return undef if (!defined($string));
3204 # Check for end of record
3205 if ($string eq "") {
3206 return $filename;
3208 $filename = $string;
3209 if (!exists($bb->{$function}->{$filename})) {
3210 $bb->{$function}->{$filename} = [];
3212 next;
3214 # Got an actual line number
3215 if (!defined($filename)) {
3216 warn("WARNING: unassigned line number in ".
3217 "$gcno_filename\n");
3218 next;
3220 # Add to list
3221 push(@{$bb->{$function}->{$filename}}, $lineno);
3222 graph_add_order($fileorder, $function, $filename);
3227 # determine_gcno_split_crc(handle, big_endian, rec_length)
3229 # Determine if HANDLE refers to a .gcno file with a split checksum function
3230 # record format. Return non-zero in case of split checksum format, zero
3231 # otherwise, undef in case of read error.
3234 sub determine_gcno_split_crc($$$)
3236 my ($handle, $big_endian, $rec_length) = @_;
3237 my $strlen;
3238 my $overlong_string;
3240 return 1 if ($gcov_version >= $GCOV_VERSION_4_7_0);
3241 return 1 if (is_compat($COMPAT_MODE_SPLIT_CRC));
3243 # Heuristic:
3244 # Decide format based on contents of next word in record:
3245 # - pre-gcc 4.7
3246 # This is the function name length / 4 which should be
3247 # less than the remaining record length
3248 # - gcc 4.7
3249 # This is a checksum, likely with high-order bits set,
3250 # resulting in a large number
3251 $strlen = read_gcno_value($handle, $big_endian, undef, 1);
3252 return undef if (!defined($strlen));
3253 $overlong_string = 1 if ($strlen * 4 >= $rec_length - 12);
3255 if ($overlong_string) {
3256 if (is_compat_auto($COMPAT_MODE_SPLIT_CRC)) {
3257 info("Auto-detected compatibility mode for split ".
3258 "checksum .gcno file format\n");
3260 return 1;
3261 } else {
3262 # Sanity check
3263 warn("Found overlong string in function record: ".
3264 "try '--compat split_crc'\n");
3268 return 0;
3272 # read_gcno_function_record(handle, graph, big_endian, rec_length)
3274 # Read a gcno format function record from handle and add the relevant data
3275 # to graph. Return (filename, function) on success, undef on error.
3278 sub read_gcno_function_record(*$$$$)
3280 my ($handle, $bb, $fileorder, $big_endian, $rec_length) = @_;
3281 my $filename;
3282 my $function;
3283 my $lineno;
3284 my $lines;
3286 graph_expect("function record");
3287 # Skip ident and checksum
3288 graph_skip($handle, 8, "function ident and checksum") or return undef;
3289 # Determine if this is a function record with split checksums
3290 if (!defined($gcno_split_crc)) {
3291 $gcno_split_crc = determine_gcno_split_crc($handle, $big_endian,
3292 $rec_length);
3293 return undef if (!defined($gcno_split_crc));
3295 # Skip cfg checksum word in case of split checksums
3296 graph_skip($handle, 4, "function cfg checksum") if ($gcno_split_crc);
3297 # Read function name
3298 graph_expect("function name");
3299 $function = read_gcno_string($handle, $big_endian);
3300 return undef if (!defined($function));
3301 # Read filename
3302 graph_expect("filename");
3303 $filename = read_gcno_string($handle, $big_endian);
3304 return undef if (!defined($filename));
3305 # Read first line number
3306 $lineno = read_gcno_value($handle, $big_endian, "initial line number");
3307 return undef if (!defined($lineno));
3308 # Add to list
3309 push(@{$bb->{$function}->{$filename}}, $lineno);
3310 graph_add_order($fileorder, $function, $filename);
3312 return ($filename, $function);
3316 # read_gcno(filename)
3318 # Read the contents of the specified .gcno file and return the following
3319 # mapping:
3320 # graph: filename -> file_data
3321 # file_data: function name -> line_data
3322 # line_data: [ line1, line2, ... ]
3324 # See the gcov-io.h file in the gcc 3.3 source code for a description of
3325 # the .gcno format.
3328 sub read_gcno($)
3330 my ($gcno_filename) = @_;
3331 my $file_magic = 0x67636e6f;
3332 my $tag_function = 0x01000000;
3333 my $tag_lines = 0x01450000;
3334 my $big_endian;
3335 my $word;
3336 my $tag;
3337 my $length;
3338 my $filename;
3339 my $function;
3340 my $bb = {};
3341 my $fileorder = {};
3342 my $instr;
3343 my $graph;
3344 local *HANDLE;
3346 open(HANDLE, "<", $gcno_filename) or goto open_error;
3347 binmode(HANDLE);
3348 # Read magic
3349 $word = read_gcno_word(*HANDLE, "file magic");
3350 goto incomplete if (!defined($word));
3351 # Determine file endianness
3352 if (unpack("N", $word) == $file_magic) {
3353 $big_endian = 1;
3354 } elsif (unpack("V", $word) == $file_magic) {
3355 $big_endian = 0;
3356 } else {
3357 goto magic_error;
3359 # Skip version and stamp
3360 graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete;
3361 while (!eof(HANDLE)) {
3362 my $next_pos;
3363 my $curr_pos;
3365 # Read record tag
3366 $tag = read_gcno_value(*HANDLE, $big_endian, "record tag");
3367 goto incomplete if (!defined($tag));
3368 # Read record length
3369 $length = read_gcno_value(*HANDLE, $big_endian,
3370 "record length");
3371 goto incomplete if (!defined($length));
3372 # Convert length to bytes
3373 $length *= 4;
3374 # Calculate start of next record
3375 $next_pos = tell(HANDLE);
3376 goto tell_error if ($next_pos == -1);
3377 $next_pos += $length;
3378 # Process record
3379 if ($tag == $tag_function) {
3380 ($filename, $function) = read_gcno_function_record(
3381 *HANDLE, $bb, $fileorder, $big_endian,
3382 $length);
3383 goto incomplete if (!defined($function));
3384 } elsif ($tag == $tag_lines) {
3385 # Read lines record
3386 $filename = read_gcno_lines_record(*HANDLE,
3387 $gcno_filename, $bb, $fileorder,
3388 $filename, $function,
3389 $big_endian);
3390 goto incomplete if (!defined($filename));
3391 } else {
3392 # Skip record contents
3393 graph_skip(*HANDLE, $length, "unhandled record")
3394 or goto incomplete;
3396 # Ensure that we are at the start of the next record
3397 $curr_pos = tell(HANDLE);
3398 goto tell_error if ($curr_pos == -1);
3399 next if ($curr_pos == $next_pos);
3400 goto record_error if ($curr_pos > $next_pos);
3401 graph_skip(*HANDLE, $next_pos - $curr_pos,
3402 "unhandled record content")
3403 or goto incomplete;
3405 close(HANDLE);
3406 ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename);
3407 graph_cleanup($graph);
3409 return ($instr, $graph);
3411 open_error:
3412 graph_error($gcno_filename, "could not open file");
3413 return undef;
3414 incomplete:
3415 graph_error($gcno_filename, "reached unexpected end of file");
3416 return undef;
3417 magic_error:
3418 graph_error($gcno_filename, "found unrecognized gcno file magic");
3419 return undef;
3420 tell_error:
3421 graph_error($gcno_filename, "could not determine file position");
3422 return undef;
3423 record_error:
3424 graph_error($gcno_filename, "found unrecognized record format");
3425 return undef;
3428 sub debug($)
3430 my ($msg) = @_;
3432 return if (!$debug);
3433 print(STDERR "DEBUG: $msg");
3437 # get_gcov_capabilities
3439 # Determine the list of available gcov options.
3442 sub get_gcov_capabilities()
3444 my $help = `$gcov_tool --help`;
3445 my %capabilities;
3447 foreach (split(/\n/, $help)) {
3448 next if (!/--(\S+)/);
3449 next if ($1 eq 'help');
3450 next if ($1 eq 'version');
3451 next if ($1 eq 'object-directory');
3453 $capabilities{$1} = 1;
3454 debug("gcov has capability '$1'\n");
3457 return \%capabilities;
3461 # parse_ignore_errors(@ignore_errors)
3463 # Parse user input about which errors to ignore.
3466 sub parse_ignore_errors(@)
3468 my (@ignore_errors) = @_;
3469 my @items;
3470 my $item;
3472 return if (!@ignore_errors);
3474 foreach $item (@ignore_errors) {
3475 $item =~ s/\s//g;
3476 if ($item =~ /,/) {
3477 # Split and add comma-separated parameters
3478 push(@items, split(/,/, $item));
3479 } else {
3480 # Add single parameter
3481 push(@items, $item);
3484 foreach $item (@items) {
3485 my $item_id = $ERROR_ID{lc($item)};
3487 if (!defined($item_id)) {
3488 die("ERROR: unknown argument for --ignore-errors: ".
3489 "$item\n");
3491 $ignore[$item_id] = 1;
3496 # is_external(filename)
3498 # Determine if a file is located outside of the specified data directories.
3501 sub is_external($)
3503 my ($filename) = @_;
3504 my $dir;
3506 foreach $dir (@internal_dirs) {
3507 return 0 if ($filename =~ /^\Q$dir\/\E/);
3509 return 1;
3513 # compat_name(mode)
3515 # Return the name of compatibility mode MODE.
3518 sub compat_name($)
3520 my ($mode) = @_;
3521 my $name = $COMPAT_MODE_TO_NAME{$mode};
3523 return $name if (defined($name));
3525 return "<unknown>";
3529 # parse_compat_modes(opt)
3531 # Determine compatibility mode settings.
3534 sub parse_compat_modes($)
3536 my ($opt) = @_;
3537 my @opt_list;
3538 my %specified;
3540 # Initialize with defaults
3541 %compat_value = %COMPAT_MODE_DEFAULTS;
3543 # Add old style specifications
3544 if (defined($opt_compat_libtool)) {
3545 $compat_value{$COMPAT_MODE_LIBTOOL} =
3546 $opt_compat_libtool ? $COMPAT_VALUE_ON
3547 : $COMPAT_VALUE_OFF;
3550 # Parse settings
3551 if (defined($opt)) {
3552 @opt_list = split(/\s*,\s*/, $opt);
3554 foreach my $directive (@opt_list) {
3555 my ($mode, $value);
3557 # Either
3558 # mode=off|on|auto or
3559 # mode (implies on)
3560 if ($directive !~ /^(\w+)=(\w+)$/ &&
3561 $directive !~ /^(\w+)$/) {
3562 die("ERROR: Unknown compatibility mode specification: ".
3563 "$directive!\n");
3565 # Determine mode
3566 $mode = $COMPAT_NAME_TO_MODE{lc($1)};
3567 if (!defined($mode)) {
3568 die("ERROR: Unknown compatibility mode '$1'!\n");
3570 $specified{$mode} = 1;
3571 # Determine value
3572 if (defined($2)) {
3573 $value = $COMPAT_NAME_TO_VALUE{lc($2)};
3574 if (!defined($value)) {
3575 die("ERROR: Unknown compatibility mode ".
3576 "value '$2'!\n");
3578 } else {
3579 $value = $COMPAT_VALUE_ON;
3581 $compat_value{$mode} = $value;
3583 # Perform auto-detection
3584 foreach my $mode (sort(keys(%compat_value))) {
3585 my $value = $compat_value{$mode};
3586 my $is_autodetect = "";
3587 my $name = compat_name($mode);
3589 if ($value == $COMPAT_VALUE_AUTO) {
3590 my $autodetect = $COMPAT_MODE_AUTO{$mode};
3592 if (!defined($autodetect)) {
3593 die("ERROR: No auto-detection for ".
3594 "mode '$name' available!\n");
3597 if (ref($autodetect) eq "CODE") {
3598 $value = &$autodetect();
3599 $compat_value{$mode} = $value;
3600 $is_autodetect = " (auto-detected)";
3604 if ($specified{$mode}) {
3605 if ($value == $COMPAT_VALUE_ON) {
3606 info("Enabling compatibility mode ".
3607 "'$name'$is_autodetect\n");
3608 } elsif ($value == $COMPAT_VALUE_OFF) {
3609 info("Disabling compatibility mode ".
3610 "'$name'$is_autodetect\n");
3611 } else {
3612 info("Using delayed auto-detection for ".
3613 "compatibility mode ".
3614 "'$name'\n");
3620 sub compat_hammer_autodetect()
3622 if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 ||
3623 $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302)
3625 info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n");
3626 return $COMPAT_VALUE_ON;
3628 return $COMPAT_VALUE_OFF;
3632 # is_compat(mode)
3634 # Return non-zero if compatibility mode MODE is enabled.
3637 sub is_compat($)
3639 my ($mode) = @_;
3641 return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON);
3642 return 0;
3646 # is_compat_auto(mode)
3648 # Return non-zero if compatibility mode MODE is set to auto-detect.
3651 sub is_compat_auto($)
3653 my ($mode) = @_;
3655 return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO);
3656 return 0;