gccr.pl

Below is the source code for gccr.pl, which was downloaded from https://github.com/eel3/gccr on July 23, 2015. This script is used by the build.sh script to merge the code coverage information for the coverage report.

#!/usr/bin/env perl
# -*- coding: utf-8; tab-width: 8 -*-
# vim: fileencoding=UTF-8 shiftwidth=8 softtabstop=8 tabstop=8

#
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005  Free Software Foundation, Inc.
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
#

# version 0.4.18 : eel3 : changed shebang, fixed indent, specified emacs/vim coding system.
# version 0.4.17 : eel3 : fixed error occur when print the line matched /%[^%]/g
# version 0.4.16 : eel3 : fixed parse error occur in line 10000 or later
# version 0.4.15 : eel3 : avoided warnings caused by uninitialized variable
# version 0.4.14 : eel3 : fixed line number indent size for over 99999 line
# version 0.4.13 : eel3 : changed warning option
# version 0.4.12 : eel3 : fixed problem that extra tab is output, and changed line number to be right-aligned
# version 0.4.11 : Nick Groesz : fix potential divide by zero
# version 0.4.10 : Nick Groesz : fixed summary in combined coverage, ignore function data, added copyright
# version 0.4.9 : Nick Groesz : added combined reporting in print_summary(), changed usage text
# version 0.4.8 : Nick Groesz : added -c option (combined coverage)
# version 0.4.7 : Nick Groesz : fixed formatting, added comments
# version 0.4.6 : Dickson Patton : fixed tagfile option, right justify counts
# version 0.4.5 : Nick Groesz : list code generated with #define macros
# version 0.4.4 : Dickson Patton : added tagfile option
# version 0.4.3 : Nick Groesz : changed around internal data structures, start of version history

use strict;
use warnings;
use Getopt::Long;

# prototypes
sub read_args();        # read in command line arguments
sub process_files();        # run through each file
sub parse_execution_data($$);   # parse the data from each file
sub print_results();        # print gcov data
sub print_summary();        # print summary (similary to gcov's summary)
sub print_usage();      # print gccr usage text

our $tool_name = 'gccr';            # name of script
our $version = 'gccr (GCC) 0.4.18';     # version of script
our $copyright = 'Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
';                      # copyright notice

# internal data
our @files;         # gcov file data
                #
                # File information:
                # file_number -  ranges from 0 to NUMBER OF FILES SPECIFIED - 1
                # $file[file_number]{'tag'} - user specified file tag
                # $file[file_number]{'name'} - name of file to be read and parsed
                #
                # $file[file_number[{'data'} contains parsed gcov data
                #
                # Line information:
                # line_number - corresponds to the line number of the GCOV file
                #       and ranges from 1 to NUMBER OF LINES
                # $data{'line'}[line_number]{'type'} - type of line
                #                      Can be set to:
                #                       no_ex -  non-executing code (gcov meta-data, header code, code that has been #ifdef'd out)
                #                       line -   an executable line of code
                #                       branch - branch execution information
                #                       call -   call execution information
                #
                # $data{'line'}[line_number]{'count'} -    number of times an executable line, branch, or call was executed
                # $data{'line'}[line_number]{'raw'} -      raw data straight from gcov file
                #                          this is only populated in the first file's data structure
                # $data{'line'}[line_number]{'line_num'} - line number in original source code
                # Execution information:
                # $data{'line_count'} -     number of executable lines
                # $data{'execution_count'} -    number of executed lines

our $file_count = 0;        # number of files read
our $line_count = 0;        # number of lines in gcov data files (should be the same for each file)
our $executable_total = 0;  # number of different executable lines across all files
our $executed_total = 0;    # number of different executable lines across all files that were executed

# options
our $opt_combined = 0;
our $opt_help = 0;      # help option
our $opt_version = 0;       # version option
our @tag_files = ();        # tagfile(s) option
our $opt_nosummary = 0;     # do not print summary

read_args();
process_files();
print_results();
unless($opt_nosummary) {
    print_summary();
}

# Summary:  read command line arguments:
# Parameters:   none
# Return:   none
sub read_args()
{
    unless( GetOptions(
        'combined' => \$opt_combined,
        'help' => \$opt_help,
        'no-summary' => \$opt_nosummary,
        'tagfile|file=s' => \@tag_files,
        'version' => \$opt_version,
              )
          ) {

        # if GetOptions returns FALSE, then incorrect options were specified
        # exit with error
        print_usage();
        exit(1);
    }

    if($opt_help) {
        print_usage();
        exit(0);
    }

    if($opt_version) {
        print "$version\n";
        print "$copyright\n";
        exit(0);
    }

    # if any number of tag files were specified, then we read tagfiles instead of command line arugments
    if(scalar(@tag_files)) {
        @files = &read_tagfiles(@tag_files);
    }else {
        # number of file arguments + number of tag arguments should equal an even number
        if(@ARGV % 2 == 1) {
            print("ERROR: file count does not match tag count\n");
            exit(1);
        }
        my $i = 0;
        while($ARGV[$i]) {
            push @files, {name => $ARGV[$i], tag => $ARGV[$i+1]};
            $i+=2;
        }
    }

    $file_count = scalar(@files);
    if($file_count < 2 ) {
        print("ERROR: at least two files must be specified\n");
        exit(1);
    }
}

# Summary:  read tagfiles specifie dwith the -t option
# Parameters:   array of names of tagfiles
# Return:   array of name/tag hashes
sub read_tagfiles($)
{
    my @tag_files = @_;
    my @files;
    my $l = 0;              # count of --tagfile=___ options
    my $m = 0;              # count of line in the current tagfile

    foreach my $file(@tag_files) {
        $l++;
        open(TAGFILE, $file) || die "ERROR: on open of tagfile $l, $file: ($!)\n";
        while(<TAGFILE>) {
            $m++;
            chomp $_;
            if ( /(^([^ ]+) *(.*)$)/ ) {
                push @files, {name => $2, tag => $3};
            }
            else {
                die "ERROR: invalid file-tag pair on line $m of tagfile $l\n";
            }
        }
        close(TAGFILE);
    }
    return(@files);
}

# Summary:  run through all the gcov files and call the parsing function
# Parameters:   none
# Return:   none
sub process_files()
{
    # the first file is used to gather raw data
    $files[0]{'data'} = parse_execution_data($files[0]{'name'},1);

    for(my $i=1;$i<$file_count;$i++) {
        $files[$i]{'data'} = parse_execution_data($files[$i]{'name'},0);
    }
}

# Summary:  parse the gcov file, populating the %data structure
# Parameters:   name of file to parse | boolean indicating whether raw (original gcov) data should be saved
#       save_raw is set to 1 on the first file parse and set to zero thereafter
# Returns:  reference to data hash
sub parse_execution_data($$)
{
    my($file,$save_raw) = @_;
    my %data;

    $data{'line_count'} = 0;        # number of executable lines in file
    $data{'execution_count'} = 0;       # number of lines that were executed in file

    stat($file);
    if(!(-r _)) {
        die("ERROR: cannot read file: $file\n");
    }
    if(!(-f _)) {
        die("ERROR: not a plain file: $file\n");
    }
    open(FILE_HANDLE,$file) || die("ERROR: cannot open file $file: $!");

    my $file_line_num = 0;
    while(<FILE_HANDLE>) {
        my $line = $_;
        $file_line_num++;

        chomp $line;

        if($line =~ /^\s+-:\s*(\d+):(.*)/) {

            # line is gcov preamble or non-executing code

            my $line_num = $1;
            my $raw = $2;

            $data{'line'}[$file_line_num]{'type'} = 'no_ex';
            $data{'line'}[$file_line_num]{'line_num'} = $line_num;

            if($save_raw) {
                $data{'line'}[$file_line_num]{'raw'} = $2;
            }
        }elsif($line =~ /^\s+#####:\s*(\d+):(.*)/) {

            # line was not executed

            my $line_num = $1;
            my $raw = $2;

            $data{'line'}[$file_line_num]{'count'} = 0;
            $data{'line'}[$file_line_num]{'type'} = 'code';
            $data{'line'}[$file_line_num]{'line_num'} = $line_num;
            $data{'line_count'}++;
            if($save_raw) {
                $data{'line'}[$file_line_num]{'raw'} = $raw;
            }
        }elsif($line =~ /^\s*(\d+):\s*(\d+):(.*)/) {

            # line was executed

            my $count = $1;
            my $line_num = $2;
            my $raw = $3;

            $data{'line'}[$file_line_num]{'count'} = $count;
            $data{'line'}[$file_line_num]{'type'} = 'code';
            $data{'line'}[$file_line_num]{'line_num'} = $line_num;
            $data{'line_count'}++;
            $data{'execution_count'}++;
            if($save_raw) {
                $data{'line'}[$file_line_num]{'raw'} = $raw;
            }
        }elsif($line =~ /^branch\s+(\d+)/) {

            # line contains branch execution information

            my $branch_num = $1;
            $data{'line'}[$file_line_num]{'num'} = $branch_num;

            if($line =~ /^branch\s+\d+\s+never executed/) {
                $data{'line'}[$file_line_num]{'count'} = 0;
                $data{'line'}[$file_line_num]{'type'} = 'branch';
            }elsif($line =~ /^branch\s+\d+\s+taken (\d+)%/) {
                $data{'line'}[$file_line_num]{'count'} = $1;
                $data{'line'}[$file_line_num]{'type'} = 'branch';
            }
            if($save_raw) {
                $data{'line'}[$file_line_num]{'raw'} = $line;
            }
        }elsif($line =~ /^call\s+(\d+)/) {

            # line contains call execution information

            my $call_num = $1;
            $data{'line'}[$file_line_num]{'num'} = $call_num;

            if($line =~ /^call\s+\d+\s+never executed/) {
                $data{'line'}[$file_line_num]{'count'} = 0;
                $data{'line'}[$file_line_num]{'type'} = 'call';
            }elsif($line =~ /^call\s+\d+\s+returns (\d+)%/) {
                $data{'line'}[$file_line_num]{'count'} = $1;
                $data{'line'}[$file_line_num]{'type'} = 'call';
            }
            if($save_raw) {
                $data{'line'}[$file_line_num]{'raw'} = $line;
            }
        }elsif($line =~ /^function/i) {
            # function data is ignored
        }else {
            # line could not be parsed

            print("ERROR: cannot parse line $file_line_num in file $file\n Is this a valid gcov file?\n");
            exit(1);
        }
    }
    close(FILE_HANDLE);

    # check to see if we should save an overall line count (common to all gcov files)
    if($save_raw) {
        $line_count = $file_line_num;
    }

    return(\%data);
}

# Summary:  print interpolated gcov information
# Parameters:   none
# Return:   none
sub print_results()
{
    my $ftab = '        ';
    my $p_op = ($line_count <= 99999) ? '%5d' : '%13d';

    for(my $line_i=1;$line_i<=$line_count;$line_i++) {

        my $raw_printed = 0;          # boolean flag to print out the line slurped in from the gcov file

        my $count_sum = 0;        # sum of executions across all files for this one line- used for combined coverage reporting

        my $never_exec = 1;       # boolean flag that is set to 0 when the current line is executed or executable

                          # in any file. used in combined coverage reporting.

        my $first_code_line_executed = 0; # boolean flag that indicates whether this is the first unique
                          # executed line of code to be found among the gcov files

        for(my $file_i=0;$file_i<$file_count;$file_i++) {
            # Note that each file is cycled through for every line even if just the raw
            # data from the first file that ends up being printed. This is because the same
            # line may be non-executing in one file and executable in another file (because code
            # may be ifdef'd out).

            my $type = $files[$file_i]{'data'}{'line'}[$line_i]{'type'};

            no warnings 'uninitialized';    # XXX: remove warining for $type
            if($type eq 'no_ex') {
                # non-executing code

                unless($raw_printed || $opt_combined) {
                    printf("$ftab-:$p_op:%s\n", $files[$file_i]{'data'}{'line'}[$line_i]{'line_num'}, $files[0]{'data'}{'line'}[$line_i]{'raw'});
                    $raw_printed = 1;
                }

                # nothing additional is printed for non-executing code
            }elsif($type eq 'code') {
                # code that is executable
                if($never_exec) {
                    # this code is exectuable, so we indicate that in the never_exec flag
                    $never_exec = 0;
                    # we only want the number of UNIQUE lines across files that are executable
                    # in the executable_total flag, so this is only incremented once for all
                    # identical lines across each file
                    $executable_total++;
                }

                unless($raw_printed || $opt_combined) {
                    printf("$ftab  $p_op:%s\n", $files[0]{'data'}{'line'}[$line_i]{'line_num'}, $files[0]{'data'}{'line'}[$line_i]{'raw'});
                    $raw_printed = 1;
                }

                my $count = $files[$file_i]{'data'}{'line'}[$line_i]{'count'};
                if($opt_combined) {
                    $count_sum += $count;
                    if($first_code_line_executed == 0 && $count > 0) {
                        $executed_total++;
                        $first_code_line_executed = 1;
                    }
                }else {
                    if($count == 0) {
                        print '     ####';
                    }else {
                        unless($first_code_line_executed) {
                            # update the unique count of code lines executed
                            # across all gcov files
                            $executed_total++;
                            $first_code_line_executed = 1;
                        }

                        my $padding = 9;
                        $padding -= length($count);
                        printf("%*s%d",$padding,' ',$count);
                    }
                    printf(":$p_op: ", $files[$file_i]{'data'}{'line'}[$line_i]{'line_num'});
                    print "$files[$file_i]{'tag'}\n";
                }
            }elsif($type eq 'branch') {
                # branch information
                my $count = $files[$file_i]{'data'}{'line'}[$line_i]{'count'};
                if($opt_combined) {
                    $count_sum += $count;
                }else {
                    if($count == 0) {
                        print "branch $files[$file_i]{'data'}{'line'}[$line_i]{'num'} never executed:$files[$file_i]{'tag'}\n";
                    }else {
                        print "branch $files[$file_i]{'data'}{'line'}[$line_i]{'num'} taken $count%:$files[$file_i]{'tag'}\n";
                    }
                }
            }elsif($type eq 'call') {
                # call information
                my $count = $files[$file_i]{'data'}{'line'}[$line_i]{'count'};

                if($opt_combined) {
                    $count_sum += $count;
                }else {
                    if($count == 0) {
                        print "call $files[$file_i]{'data'}{'line'}[$line_i]{'num'} never executed:$files[$file_i]{'tag'}\n";
                    }else {
                        print "call $files[$file_i]{'data'}{'line'}[$line_i]{'num'} returns $count%:$files[$file_i]{'tag'}\n";
                    }
                }
            }
        }

        if($opt_combined) {
            # if the combined coverage flag is set then no information is printed in the above for loop
            # count information is summed into $count_sum and printed on a single line

            my $type = $files[0]{'data'}{'line'}[$line_i]{'type'};

            no warnings 'uninitialized';    # XXX: remove warining for $type
            if($type eq 'no_ex' || $type eq 'code') {
                # line is either non-executable or executable code

                if($never_exec) {
                    # code line is not executable in any file

                    printf("$ftab-:$p_op:%s\n", $files[0]{'data'}{'line'}[$line_i]{'line_num'}, $files[0]{'data'}{'line'}[$line_i]{'raw'});
                }else {

                    # line is executable in at least one file

                    if($count_sum == 0) {
                        print '    #####';
                    }else {
                        my $padding = 9;
                        $padding -= length($count_sum);
                        printf("%*s%d",$padding,' ',$count_sum);
                    }
                    printf(":$p_op:%s\n", $files[0]{'data'}{'line'}[$line_i]{'line_num'}, $files[0]{'data'}{'line'}[$line_i]{'raw'});
                }
            }elsif($type eq 'branch') {
                # branch information
                if($count_sum == 0) {
                    print "branch $files[0]{'data'}{'line'}[$line_i]{'num'} never executed\n";
                }else {
                    my $percentage = $count_sum / $file_count;
                    print "branch $files[0]{'data'}{'line'}[$line_i]{'num'} taken $percentage%\n";
                }
            }elsif($type eq 'call') {
                # call information

                if($count_sum == 0) {
                    print "call $files[0]{'data'}{'line'}[$line_i]{'num'} never executed\n";
                }else {
                    my $percentage = $count_sum / $file_count;
                    print "call $files[0]{'data'}{'line'}[$line_i]{'num'} returns $percentage%\n";
                }
            }
        }
    }
}

# Summary:  prints the line execution percentages for each file and a percentage for all files combined
# Parameters:   none
# Return:   none
sub print_summary()
{
    for(my $file_i=0;$file_i<$file_count;$file_i++) {
        my $file_line_count = $files[$file_i]{'data'}{'line_count'};
        my $percentage;
        if($file_line_count) {
            $percentage = ($files[$file_i]{'data'}{'execution_count'} / $file_line_count) * 100;
        }else {
            $percentage = 0;
        }
        $percentage = sprintf('%.2f',$percentage);
        print "$percentage% of $file_line_count lines executed on target $files[$file_i]{'tag'}\n";
    }
    my $overall_percentage;
    if($executable_total) {
        $overall_percentage = ($executed_total / $executable_total) * 100;
    }else {
        $overall_percentage = 0;
    }
    $overall_percentage = sprintf('%.2f',$overall_percentage);
    print "$overall_percentage% of $executable_total lines executed across all files\n";
}

# Summary:  print tool usage information
# Parameters:   none
# Return:   none
sub print_usage()
{
    print <<END_USAGE;
Usage: ./$tool_name [options] <file name> <target id> <file name> <target id> [file name] [target id]...
Use $tool_name to compare gcov files generated on different platforms or targets.
  -h, --help                      Print this help, then exit
  -v, --version                   Print version number, then exit
Input Options:
  -t, --tagfile                   Take file-tag assignments from a file, not from command-line
Ouput Options:
  -c, --combined          Print combined coverage
  -n, --no-summmary       Do not print summary
END_USAGE
}