#!/usr/bin/perl
# SPDX-License-Identifier: MIT
use strict;
use warnings;
use Getopt::Long;
BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Termcap'; }
use Pod::Usage;
use Pod::Man;
use IO::File;
use IO::Zlib;

my $prefix = qr ".*?(linux)\w*/";

my $title = "";

my %used_func;
my %all_func;
my %all_branch;
my %all_line;
my %used_source;
my %record;
my %files;
my @func_include_regexes;
my @func_exclude_regexes;
my @branch_include_regexes;
my @branch_exclude_regexes;
my %test_names;
my @src_include_regexes;
my @src_exclude_regexes;
my $source_dir = ".";
my $can_filter_lines = 1;
my $ignore_lines_without_functions = 1;
my $ignore_branches_on_headers = 1;
my $has_branch_filter;

my $verbose = 0;
my $ignore_unused = 0;
my $skip_func = 0;

my $has_json_support = eval {
	require Cpanel::JSON::XS;
	Cpanel::JSON::XS->import(qw(decode_json));
	1;
};

if (!$has_json_support) {
	print "Warning: System doesn't have Cpanel::JSON::XS. Can't use gcov directly.\n";
}

sub is_function_excluded($)
{
	return 0 if (!@func_include_regexes && !@func_exclude_regexes);

	my $func = shift;

	# Handle includes first, as, when there are both include and exclude
	# includes should take preference, as they can be overriding exclude
	# rules
	foreach my $r (@func_include_regexes) {
	    return 0 if ($func =~ m/$r/);
	}

	foreach my $r (@func_exclude_regexes) {
		return 1 if ($func =~ m/$r/);
	}

	# If there are no exclude regexes, only include functions that are
	# explicitly included.
	if ($#func_exclude_regexes == 0) {
		return 1;
	}

	return 0;
}

sub is_file_excluded($)
{
	my $s = shift;

	return 0 if (!@src_include_regexes && !@src_exclude_regexes);

	foreach my $r (@src_exclude_regexes) {
		return 1 if ($s =~ m/$r/);
	}

	return 0 if (!@src_include_regexes);

	foreach my $r (@src_include_regexes) {
		return 0 if ($s =~ m/$r/);
	}

	return 1;
}

sub is_branch_excluded($)
{
	return 0 if (!@branch_include_regexes && !@branch_exclude_regexes);

	my $branch = shift;

	# Handle includes first, as, when there are both include and exclude
	# includes should take preference, as they can be overriding exclude
	# rules
	foreach my $r (@branch_include_regexes) {
	    return 0 if ($branch =~ m/$r/);
	}

	foreach my $r (@branch_exclude_regexes) {
		return 1 if ($branch =~ m/$r/);
	}

	# If there are no exclude regexes, only include branches that are
	# explicitly included.
	if ($#branch_exclude_regexes == 0) {
		return 1;
	}

	return 0;
}

# Use something that comes before any real function
my $before_sf = "!!!!";

sub parse_json_gcov_v1($$)
{
	my $file = shift;
	my $json = shift;

	my $has_func = 0;
	my $ignore = 0;

	my $cur_test = $file;
	$cur_test =~ s#^.*/##;
	$cur_test =~ s#\.json$##;
	$test_names{$cur_test} = 1;

	# Store the common JSON data into the record
	for my $key (keys %$json) {
		next if ($key eq "files");
		next if ($key eq "functions");
		next if ($key eq "lines");
		# Store any extra data
		$record{$key} = $json->{$key};
	}
	# Store test name at the record
	$record{tests}{$cur_test} = 1;

	my %cached;
	for my $file_ref (@{$json->{'files'}}) {
		my $source = $file_ref->{'file'};
		my $was_used = 0;

		$files{$source} = 1;
		next if is_file_excluded($source);

		# Parse functions
		for my $func_ref (@{$file_ref->{'functions'}}) {
			my $func = $func_ref->{'name'};

			next if is_function_excluded($func);

			# Negative gcov results are possible, as reported at:
			# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
			# Lcov ignores those. So, let's do the same here.
			$func_ref->{'execution_count'} = 0 if ($func_ref->{'execution_count'} < 0);

			# Store the record
			for my $key (keys %$func_ref) {
				if ($key eq "execution_count") {
					$record{files}{$source}{func}{$func}{$key} += $func_ref->{$key};
				} else {
					$record{files}{$source}{func}{$func}{$key} = $func_ref->{$key};
				}
			}

			$all_func{$func}{$source}->{ln} = $func_ref->{'start_line'};
			$all_func{$func}{$source}->{end_ln} = $func_ref->{'end_line'};

			if ($func_ref->{'execution_count'} > 0) {
				$used_func{$func}{$source}->{count} += $func_ref->{'execution_count'};
				$was_used = 1;
			}
		}

		# Parse lines and branches
		for my $line_ref (@{$file_ref->{'lines'}}) {
			my $ln = $line_ref->{'line_number'};

			# Negative gcov results are possible, as reported at:
			# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
			# Lcov ignores those. So, let's do the same here.
			$line_ref->{'count'} = 0 if ($line_ref->{'count'} < 0);

			my $func = $line_ref->{'function_name'};
			if (!$func) {
				$func = $before_sf;
			} else {
				next if is_function_excluded($func);
			}

			# Store the record
			for my $key (keys %$line_ref) {
				next if ($key eq "line_number");

				# Branches will be handled in separate
				next if ($key eq "branches");
				if ($key eq "count") {
					$record{files}{$source}{line}{$ln}{$key} += $line_ref->{$key};
				} else {
					$record{files}{$source}{line}{$ln}{$key} = $line_ref->{$key};
				}
			}
			$all_line{$source}{$ln} += $line_ref->{'count'};

			if ($ignore_branches_on_headers) {
				next if ($source =~ m/\.h$/);
			}

			my $i = 0;
			for my $branch_ref (@{$line_ref->{'branches'}}) {
				my $where = sprintf "%d,%d,%d", $ln, 0, $i;

				# Filter out branches
				if ($has_branch_filter) {
					if (!$cached{$source}) {
						open IN, "$source_dir/$source" || die "File $source_dir/$source not found. Can't filter branches\n";
						push @{$cached{$source}}, <IN>;
						close IN;
					}
					my $nlines = scalar(@{$cached{$source}});
					if ($ln > $nlines) {
						die "$source:$ln line is bigger than the number of lines at the file ($nlines lines)\n";
						return;
					}
					next if (is_branch_excluded($cached{$source}[$ln - 1]));
				}

				if ($func eq $before_sf) {
					# Ignore DA/BRDA that aren't associated with
					# functions. Those are present on header files
					# (maybe defines?)
					next if ($ignore_lines_without_functions);

					# Otherwise place them in separate
					$func = $before_sf;
				} else {
					next if is_function_excluded($func);

					$all_branch{$source}{$where}{func} = $func;
				}

				# Negative gcov results are possible, as
				# reported at:
				# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
				# Lcov ignores those. So, let's do the same
				# here.
				$branch_ref->{'count'} = 0 if ($branch_ref->{'count'} < 0);

				for my $key (keys %$branch_ref) {
					if ($key eq "count") {
						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} += $branch_ref->{$key};
					} else {
						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} = $branch_ref->{$key};
					}
				}

				$all_branch{$source}{$where}{count} += $branch_ref->{'count'};
				$was_used = 1 if ($branch_ref->{'count'} > 0);

				$i++;
			}
			if (!defined($record{files}{$source}{line}{$ln}{branches})) {
				@{$record{files}{$source}{line}{$ln}{branches}} = ();
			}
		}
		next if ($ignore_unused && !$was_used);
		$used_source{$source} = 1;
	}

	# As the record was changed, we need to use a different format name
	$record{format_version} = "parse_info v1.0";
}

sub parse_json_internal_format_v1($$)
{
	my $file = shift;
	my $json = shift;

	my $has_func = 0;
	my $ignore = 0;
	my %cached;

	# Store the common JSON data into the record
	for my $key (keys %$json) {
		next if ($key eq "files");
		next if ($key eq "functions");
		next if ($key eq "lines");
		# Store any extra data
		$record{$key} = $json->{$key};
	}

	for my $test (keys %{$json->{'tests'}}) {
		$test_names{$test} = 1;
	}

	for my $source (keys %{$json->{'files'}}) {
		$files{$source} = 1;
		my $was_used = 0;

		next if is_file_excluded($source);

		my $file_ref = \%{$json->{'files'}{$source}};

		# Parse functions
		for my $func (keys %{$file_ref->{func}}) {
			next if is_function_excluded($func);

			my $func_ref = \%{$file_ref->{func}{$func}};

			# Store the record
			for my $key (keys %$func_ref) {
				if ($key eq "execution_count") {
					$record{files}{$source}{func}{$func}{$key} += $func_ref->{$key};
				} else {
					$record{files}{$source}{func}{$func}{$key} = $func_ref->{$key};
				}
			}

			$all_func{$func}{$source}->{ln} = $func_ref->{'start_line'};
			$all_func{$func}{$source}->{end_ln} = $func_ref->{'end_line'};

			if ($func_ref->{'execution_count'} > 0) {
				$used_func{$func}{$source}->{count} += $func_ref->{'execution_count'};
				$was_used = 1;
			}
		}

		# Parse lines and branches
		for my $ln (keys %{$file_ref->{line}}) {
			my $line_ref = \%{$file_ref->{line}{$ln}};
			my $func = $line_ref->{'function_name'};
			if (!$func) {
				# Ignore DA/BRDA that aren't associated with
				# functions. Those are present on header files
				# (maybe defines?)
				next if (@func_include_regexes);

				# Otherwise place them in separate
				$func = $before_sf;
			} else {
				next if is_function_excluded($func);
			}

			# Store the record
			for my $key (keys %$line_ref) {
				next if ($key eq "line_number");

				# Branches will be handled in separate
				next if ($key eq "branches");
				if ($key eq "count") {
					$record{files}{$source}{line}{$ln}{$key} += $line_ref->{$key};
				} else {
					$record{files}{$source}{line}{$ln}{$key} = $line_ref->{$key};
				}
			}
			$all_line{$source}{$ln} += $line_ref->{'count'};

			if ($ignore_branches_on_headers) {
				next if ($source =~ m/\.h$/);
			}

			my $i = 0;
			for my $branch_ref (@{$line_ref->{'branches'}}) {
				my $taken = $branch_ref->{'count'};
				my $where = sprintf "%d,%d,%d", $ln, 0, $i;


				# Filter out branches
				if ($has_branch_filter) {
					if (!$cached{$source}) {
						open IN, "$source_dir/$source" || die "File $source_dir/$source not found. Can't filter branches\n";
						push @{$cached{$source}}, <IN>;
						close IN;
					}
					my $nlines = scalar(@{$cached{$source}});
					if ($ln > $nlines) {
						die "$source:$ln line is bigger than the number of lines at the file ($nlines lines)\n";
						return;
					}
					next if (is_branch_excluded($cached{$source}[$ln - 1]));
				}

				for my $key (keys %$branch_ref) {
					next if (!$record{files}{$source}{line}{$ln}{branches});
					if ($key eq "count") {
						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} += $branch_ref->{$key};
					} else {
						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} = $branch_ref->{$key};
					}
				}

				$all_branch{$source}{$where}{count} += $taken;
				$was_used = 1 if ($taken > 0);
				$i++;
			}
			if (!defined($record{files}{$source}{line}{$ln}{branches})) {
				@{$record{files}{$source}{line}{$ln}{branches}} = ();
			}
		}
		next if ($ignore_unused && !$was_used);
		$used_source{$source} = 1;
	}
}

sub read_json($)
{
	my $file = shift;

	if (!$has_json_support) {
		die "Can't parse json as system doesn't have Cpanel::JSON::XS.\n";
	}

	# Read JSON data
	my $fh;
	if ($file =~ m/\.gz$/) {
		$fh = IO::Zlib->new($file, "rb") or die "can't open $file";
	} else {
		$fh = IO::File->new($file) or die "can't open $file";
	}
	print "reading $file...\n" if ($verbose);
	while (<$fh>) {
		my $json = eval {
			Cpanel::JSON::XS::decode_json($_)
		};

		if (!$json) {
			printf "Failed to parse file $file, line $.\n";
			next;
		}
		if ($json->{'format_version'} eq '1') {
			parse_json_gcov_v1($file, $json);
		} elsif ($json->{'format_version'} eq 'parse_info v1.0') {
			parse_json_internal_format_v1($file, $json);
		} else {
			if ($json->{'format_version'}) {
				die "Can't parse JSON version %d on file $file\n", $json->{'format_version'};
			}
			die "Unknown JSON format on file $file\n";
		}
	}
	$fh->close() or die "Failed to close file $file";
}

sub read_info($)
{
	my $file = shift;
	my $was_used = 0;
	my $has_func = 0;
	my $ignore = 0;
	my $source = $before_sf;
	my $func = $before_sf;
	my $cur_test = "";
	my %cached;

	# Info files don't contain functions for lines. So, they can't
	# be used to filter lines and branches used inside functions.
	$can_filter_lines = 0;

	# First step: parse data

	# For details on .info file format, see "man geninfo"
	# http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
	my $fh;
	if ($file =~ m/\.gz$/) {
		$fh = IO::Zlib->new($file, "rb") or die "can't open $file";
	} else {
		$fh = IO::File->new($file) or die "can't open $file";
	}
	print "reading $file...\n" if ($verbose);
	while (<$fh>) {
		# TN:<test name>
		if (m/^TN:(.*)/) {
			if ($1 ne $cur_test) {
				$cur_test = $1;
				$test_names{$cur_test} = 1;
				$record{tests}{$cur_test} = 1;
			}
			$source = $before_sf;
			$func = $before_sf;
			next;
		}

		# SF:<absolute path to the source file>
		if (m/^[SK]F:(.*)/) {
			$source = $1;

			$was_used = 0;
			$has_func = 0;
			$func = $before_sf;
			$files{$source} = 1;

			# Just ignore files explictly set as such
			$ignore = is_file_excluded($source);
			next;
		}

		# End of record
		if (m/^end_of_record/) {
			if (!$source) {
				print "bad end_of_record field at $file, line $. Ignoring...\n";
				next;
			}

			my $s = $source;

			$source = $before_sf;
			$func = $before_sf;

			next if ($ignore);
			next if ($ignore_unused && !$was_used);

			# Mark that the source was not ignored
			$used_source{$s} = 1;
			next;
		}

		next if ($ignore);

		# Function coverage

		# FN:<line number of function start>,<function name>
		# Note: /w+ intentionally removes IPA gcc-optimization names, as
		# this is more related to branch coverage
		if (m/^FN:(-?\d+),(\w+)/) {
			my $ln = $1;

			$func = $2;
			$has_func = 1;

			if (is_function_excluded($func)) {
				$skip_func = 1;
				next;
			}

			$skip_func = 0;

			$record{files}{$source}{func}{$func}{start_line} = $ln;
			$all_func{$func}{$source}->{ln} = $ln;
			next;
		}

		# Parse functions that were actually used
		# FNDA:<execution count>,<function name>
		# Note: /w+ intentionally removes IPA gcc-optimization names, as
		# this is more related to branch coverage
		if (m/^FNDA:(-?\d+),(\w+)/) {
			my $count = $1;

			# Negative gcov results are possible, as reported at:
			# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
			# Lcov ignores those. So, let's do the same here.
			next if ($count <= 0);

			$func = $2;
			$has_func = 1;

			if (is_function_excluded($func)) {
				$skip_func = 1;
				next;
			}

			$skip_func = 0;
			$was_used = 1;

			$record{files}{$source}{func}{$func}{execution_count} += $count;
			$used_func{$func}{$source}->{count} += $count;
			next;
		}

		# Ignore data from skipped functions
		next if ($skip_func);

		# Ignore DA/BRDA that aren't associated with functions
		# Those are present on header files (maybe defines?)
		next if (@func_include_regexes && !$has_func);

		# FNF:<number of functions found>
		if (m/^FNF:(-?\d+)/) {
			next;
		}
		# FNH:<number of function hit>
		if (m/^FNH:(-?\d+)/) {
			next;
		}

		# Branch coverage

		# BRDA:<line number>,<block number>,<branch number>,<taken>
		if (m/^BRDA:(-?\d+),(-?\d+),(-?\d+),(.*)/) {
			my $ln = $1;
			my $block = $2;
			my $branch = $3;
			my $taken = $4;

			if ($ignore_branches_on_headers) {
				next if ($source =~ m/\.h$/);
			}

			# Filter out branches
			if ($has_branch_filter) {
				if (!$cached{$source}) {
					open IN, "$source_dir/$source" || die "File $source_dir/$source not found. Can't filter branches\n";
					push @{$cached{$source}}, <IN>;
					close IN;
				}
				my $nlines = scalar(@{$cached{$source}});
				die "File $source_dir/$source not found or it is empty. Can't filter branches\n" if (!$nlines);
				if ($ln > $nlines) {
					die "$source:$ln line is bigger than the number of lines at the file ($nlines lines)\n";
					return;
				}
				next if (is_branch_excluded($cached{$source}[$ln - 1]));
			}

			if ($block != 0) {
				print "Warning: unexpected block $block at line $.\n";
			}

			my $where = "$ln,$block,$branch";

			$taken = 0 if ($taken eq '-');

			# Negative gcov results are possible, as reported at:
			# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
			# Lcov ignores those. So, let's do the same here.
			$taken = 0 if ($taken < 0);

			$was_used = 1 if ($taken > 0);

			$record{files}{$source}{line}{$ln}{branches}[$branch]{count} += $taken;
			$all_branch{$source}{$where}{count} += $taken;
			next;
		}

		# BRF:<number of branches found>
		if (m/^BRF:(-?\d+)/) {
			next;
		}
		# BRH:<number of branches hit>
		if (m/^BRH:(-?\d+)/) {
			next;
		}

		# Line coverage

		# DA:<line number>,<execution count>[,<checksum>]
		if (m/^DA:(-?\d+),(-?\d+)(,.*)?/) {
			my $ln = $1;
			my $count = $2;

			# Negative gcov results are possible, as reported at:
			# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
			# Lcov ignores those. So, let's do the same here.
			$count = 0 if ($count < 0);

			$was_used = 1 if ($count > 0);

			$record{files}{$source}{line}{$ln}{count} += $count;
			if (!defined($record{files}{$source}{line}{$ln}{branches})) {
				@{$record{files}{$source}{line}{$ln}{branches}} = ();
			}

			$all_line{$source}{$ln} += $count;

			next;
		}

		# LF:<number of instrumented lines>
		if (m/^LF:(-?\d+)/) {
			next;
		}

		# LH:<number of lines with a non-zero execution count>
		if (m/^LH:(-?\d+)/) {
			next;
		}

		printf("Warning: invalid line: $_");
	}

	$fh->close() or die "Failed to close file $file";
}

sub sort_where($$)
{
	my @a = split ",", shift;
	my @b = split ",", shift;
	my $ret;

	$ret = $a[0] <=> $b[0];
	return $ret if ($ret);

	$ret = $a[1] <=> $b[1];
	return $ret if ($ret);

	return $a[2] <=> $b[2];
}

sub write_json_file($)
{
	my $fname = shift;

	if (!$has_json_support) {
		die "Can't parse json as system doesn't have Cpanel::JSON::XS.\n";
	}

	my $data = eval {
		Cpanel::JSON::XS::encode_json(\%record)
	};

	if ($fname =~ m/\.gz$/) {
		my $fh = IO::Zlib->new($fname, "wb") or die "can't open $fname";
		print $fh $data or die "Failed to write to $fname";
		$fh->close or die "Failed to write to $fname";
	} else {
		open OUT, ">$fname" or die "Can't open $fname";
		print OUT $data or die "Failed to write to $fname";
		close OUT or die "Failed to close to $fname";
	}
}

sub write_info_file($)
{
	my $fname = shift;
	my $data = "";

	if ($title eq "") {
		foreach my $testname(sort keys %test_names) {
			$data .= "TN:$testname\n";
		}
	} else {
		$data .= "TN:$title\n";
	}

	# Fills $data with the contents to be stored at the file
	foreach my $source(sort keys %{$record{files}}) {
		next if (!$used_source{$source});

		if ($source ne $before_sf) {
			$data .= "SF:$source\n";
		}

		foreach my $func(sort keys %{ $record{files}{$source}{func} }) {
			if ($func ne $before_sf) {
				my $fn;
				my $fnda;

				if (defined($record{files}{$source}{func}{$func}{start_line})) {
					$data .= "FN:" . $record{files}{$source}{func}{$func}{start_line} . ",$func\n";
				}
				if (defined($record{files}{$source}{func}{$func}{execution_count})) {
					$data .= "FNDA:" . $record{files}{$source}{func}{$func}{execution_count} . ",$func\n";
				}

			}
		}

		foreach my $ln(sort { $a <=> $b } keys %{ $record{files}{$source}{line} }) {
			$data .= "DA:$ln," . $record{files}{$source}{line}{$ln}{count} . "\n";
			next if (!$record{files}{$source}{line}{$ln}{branches});
			for (my $i = 0; $i < scalar @{$record{files}{$source}{line}{$ln}{branches}}; $i++) {
				my $taken = $record{files}{$source}{line}{$ln}{branches}[$i]{count};
				$taken = "-" if (!$taken);
				$data .= "BRDA:$ln,0,$i,$taken\n";
			}
		}
		$data .= "end_of_record\n";
	}
	if ($fname =~ m/\.gz$/) {
		my $fh = IO::Zlib->new($fname, "wb") or die "can't open $fname";
		print $fh $data or die "Failed to write to $fname";
		$fh->close or die "Failed to write to $fname";
	} else {
		open OUT, ">$fname" or die "Can't open $fname";
		print OUT $data or die "Failed to write to $fname";
		close OUT or die "Failed to close to $fname";
	}
}

sub print_code_coverage($$$)
{
	my $print_used = shift;
	my $print_unused = shift;
	my $show_lines = shift;

	return if (!$print_used && !$print_unused);

	my $prev_file = "";

	foreach my $func (sort keys(%all_func)) {
		my @keys = sort keys(%{$all_func{$func}});
		foreach my $file (@keys) {
			my $count = 0;
			my $name;

			if ($used_func{$func}) {
				if ($used_func{$func}->{$file}) {
					$count = $used_func{$func}->{$file}->{count};
				}
			}

			if ($show_lines) {
				my $ln = $all_func{$func}{$file}->{ln};
				$file =~ s,$prefix,linux/,;
				$name = "$func() from $file";
				$name .= ":" . $ln if ($ln);
			} elsif (scalar @keys > 1) {
				$file =~ s,$prefix,linux/,;
				$name = "$func() from $file:";
			} else {
				$name = "$func():";
			}
			if ($print_unused) {
				if (!$count) {
					print "$name unused\n";
				} elsif ($print_used) {
					print "$name executed $count times\n";
				}
			} elsif ($count) {
				print "$name executed $count times\n";
			}
		}
	}
}

my %stats;

sub gen_stats()
{
	# per-line coverage statistics
	$stats{"line_count"} = 0;
	$stats{"line_reached"} = 0;

	foreach my $source (keys(%all_line)) {
		next if (!$used_source{$source});

		foreach my $where (keys(%{$all_line{$source}})) {
			$stats{"line_count"}++;
			$stats{"line_reached"}++ if ($all_line{$source}{$where} != 0);
		}
	}

	# per-function coverage statistics
	$stats{"func_count"} = 0;
	$stats{"func_used"} = 0;

	foreach my $func (keys(%all_func)) {
		foreach my $file (keys(%{$all_func{$func}})) {
			$stats{"func_count"}++;
			if ($used_func{$func}) {
				if ($used_func{$func}->{$file}) {
					$stats{"func_used"}++;
				}
			}
		}
	}

	# per-branch coverage statistics
	$stats{"branch_count"} = 0;
	$stats{"branch_reached"} = 0;

	foreach my $source (keys(%all_branch)) {
		next if (!$used_source{$source});

		foreach my $where (keys(%{$all_branch{$source}})) {
			$stats{"branch_count"}++;
			$stats{"branch_reached"}++ if ($all_branch{$source}{$where}{count} != 0);
		}
	}

	# per-file coverage stats
	$stats{"all_files"} = scalar keys(%files);
	$stats{"filtered_files"} = scalar keys(%{$record{files}});
	$stats{"used_files"} = scalar keys(%used_source);
}

sub print_summary()
{
	if ($stats{"line_count"}) {
		my $percent = 100. * $stats{"line_reached"} / $stats{"line_count"};
		printf "  lines......: %.1f%% (%d of %d lines)\n",
			$percent, $stats{"line_reached"}, $stats{"line_count"};
	} else {
		print "No line coverage data.\n";
	}

	if ($stats{"func_count"}) {
		my $percent = 100. * $stats{"func_used"} / $stats{"func_count"};
		printf "  functions..: %.1f%% (%d of %d functions)\n",
			$percent, $stats{"func_used"}, $stats{"func_count"};
	} else {
		print "No functions reported. Wrong filters?\n";
		return;
	}

	if ($stats{"branch_count"}) {
		my $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"};
		printf "  branches...: %.1f%% (%d of %d branches)\n",
			$percent, $stats{"branch_reached"}, $stats{"branch_count"};
	} else {
		print "No branch coverage data.\n";
	}
}

sub open_filter_file($$$)
{
	my $fname = shift;
	my $include = shift;
	my $exclude = shift;
	my $match_str = "";
	my $not_match_str = "";
	my $filter = "";
	my $i;

	# Handle regexes that came from command line params

	for ($i = 0;$i < scalar(@{$include}); $i++) {
		my $op = @{$include}[$i];
		$match_str .= sprintf "m`$op` ";
		@{$include}[$i] = qr /$op/;
	}

	for ($i = 0;$i < scalar(@{$exclude}); $i++) {
		my $op = @{$exclude}[$i];
		$not_match_str .= sprintf "m`$op` ";
		@{$exclude}[$i] = qr /$op/;
	}

	if ($fname) {
		open IN, $fname or die "Can't open $fname";
		while (<IN>) {
			s/^\s+//;
			s/\s+$//;
			next if (m/^#/ || m/^$/);
			if (m/^([+\-])\s*(.*)/) {
				if ($1 eq "+") {
					$match_str .= sprintf "m`$2` ";
					push @{$include}, qr /$2/;
				} else {
					$not_match_str .= sprintf "m`$2` ";
					push @{$exclude}, qr /$2/;
				}
			} else {
				$match_str .= sprintf "m`$_` ";
				push @{$include}, qr /$_/;
			}
		}
		close IN;
	}

	$filter .= "not match: $not_match_str" if ($not_match_str);
	if ($match_str) {
		$filter .= "and " if ($filter ne "");
		$filter .= "match: $match_str";
	}

	$filter =~ s/\s+$//;

	return $filter;
}

my $gen_report;
my $css_file;
my $html_prolog;
my $html_epilog;
my %report;

sub generate_report($)
{
	my $filter_str = shift;
	my $percent;
	my $prolog = "";
	my $epilog = "";
	my @info_files = sort(keys %report);

	$title = "Code coverage results" if ($title eq "");

	if ($html_prolog) {
		open IN, $html_prolog or die "Can't open prolog file";
		$prolog .= $_ while (<IN>);
		close IN;
	}

	if ($html_epilog) {
		open IN, $html_epilog or die "Can't open epilog file";
		$epilog .= $_ while (<IN>);
		close IN;
	}

	# Re-generate the hashes used to report stats in order to procuce the
	# Total results

	%used_func = ();
	%all_func = ();
	%all_branch = ();
	%all_line = ();
	%used_source = ();
	%files = ();
	%test_names = ();
	my @all_func_keys;
	my %info_files_with_func;

	foreach my $f (@info_files) {
		foreach my $source (keys(%{$report{$f}{"all_line"}})) {
			$used_source{$source} = 1 if ($report{$f}{"used_source"});
			foreach my $where (keys(%{$report{$f}{"all_line"}{$source}})) {
				$all_line{$source}{$where} += $report{$f}{"all_line"}{$source}{$where};
			}
		}
		foreach my $func (keys(%{$report{$f}{"all_func"}})) {
			foreach my $file (keys(%{$report{$f}{"all_func"}{$func}})) {
				$all_func{$func}{$file}->{ln} = $report{$f}{"all_func"}{$func}{$file}->{ln};
				$used_func{$func}->{$file} = 1 if ($report{$f}{"used_func"}{$func}->{$file});
				if ($report{$f}{"used_func"}{$func}->{$file}) {
					$used_func{$func}->{$file} = 1;
					if (!$info_files_with_func{"$file $func"}) {
						$info_files_with_func{"$file $func"} = 1;
					} else {
						$info_files_with_func{"$file $func"}++;
					}
				}
			}
		}
		foreach my $source (keys(%{$report{$f}{"all_branch"}})) {
			foreach my $where (keys(%{$report{$f}{"all_branch"}{$source}})) {
				$all_branch{$source}{"$where"}{count} += $report{$f}{"all_branch"}{$source}{$where}{count};
			}
		}
		for my $source(keys(%{$report{$f}{"files"}})) {
			$files{$source} = 1;
			$used_source{$source} = 1 if ($report{$f}{"used_source"}{$source});
		}
		for my $test(keys(%{$report{$f}{"test_names"}})) {
			$test_names{$test} = 1;
		}
	}
	gen_stats();

	# Gen code coverage set comparision counters
	my $common_func_count = 0;
	foreach my $k (keys %info_files_with_func) {
		$common_func_count++ if ($info_files_with_func{$k} == scalar (@info_files));
	}
	foreach my $f (@info_files) {
		$report{$f}{"more_func"} = 0;
		$report{$f}{"uniq_func"} = 0;
		foreach my $func (keys(%{$report{$f}{"all_func"}})) {
			foreach my $file (keys(%{$report{$f}{"all_func"}{$func}})) {
				next if (!$report{$f}{"used_func"}{$func}->{$file});
				next if ($info_files_with_func{"$file $func"} == scalar (@info_files));

				$report{$f}{"more_func"}++;
				$report{$f}{"uniq_func"}++ if ($info_files_with_func{"$file $func"} == 1);
			}
		}
	}

	# Colors for the html output

	my $red    = "style=\"background-color:#ffb3b3\"";
	my $yellow = "style=\"background-color:#ffffb3\"";
	my $green  = "style=\"background-color:#d9ffd9\"";

	# Open report file

	open OUT, ">$gen_report" or die "Can't open $gen_report";

	print OUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";

	print OUT "<html lang=\"en\">\n\n";
	print OUT "<head>\n";
	print OUT "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n";
	print OUT "  <title>$title</title>\n";
	print OUT "  <link rel=\"stylesheet\" type=\"text/css\" href=\"$css_file\">\n" if ($css_file);
	print OUT "</head>\n\n<body>\n$prolog";

	print OUT "  <h1>$title</h1>\n";

	print OUT "  <h2>Summary</h2>\n";
	# Generates a table containing the code coverage statistics per input

	print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n  <tr>\n";
	print OUT "    <th></th>\n";
	foreach my $f (@info_files) {
		print OUT "    <th>$f</th>\n";
	}
	print OUT "    <th>TOTAL</th>\n";
	print OUT "    <th>Total count</th>\n";
	print OUT "  </tr><tr>\n";

	print OUT "    <td><b>Functions</b></td>\n";
	foreach my $f (@info_files) {
		my %st = %{$report{$f}{"stats"}};
		if ($st{"func_count"}) {
			$percent = 100. * $st{"func_used"} / $st{"func_count"};

			printf OUT "    <td>%.1f%%</td>\n", $percent;
		} else {
			print OUT "    <td>N. A.</td>\n";
		}
	}
	if ($stats{"func_count"}) {
		$percent = 100. * $stats{"func_used"} / $stats{"func_count"};

		printf OUT "    <td>%.1f%%</td>\n", $percent;
	} else {
		print OUT "    <td>N. A.</td>\n";
	}
	print OUT "    <td>" . $stats{"func_count"} . "</td>\n";
	print OUT "  </tr><tr>\n";

	print OUT "    <td><b>Branches</b></td>\n";
	foreach my $f (@info_files) {
		my %st = %{$report{$f}{"stats"}};
		if ($st{"branch_count"}) {
			$percent = 100. * $st{"branch_reached"} / $st{"branch_count"};

			printf OUT "    <td>%.1f%%</td>\n", $percent;
		} else {
			print OUT "    <td>N. A.</td>\n";
		}
	}
	if ($stats{"branch_count"}) {
		$percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"};

		printf OUT "    <td>%.1f%%</td>\n", $percent;
	} else {
		print OUT "    <td>N. A.</td>\n";
	}
	print OUT "    <td>" . $stats{"branch_count"} . "</td>\n";
	print OUT "  </tr><tr>\n";

	print OUT "    <td><b>Lines</b></td>\n";
	foreach my $f (@info_files) {
		my %st = %{$report{$f}{"stats"}};

		if ($st{"line_count"}) {
			$percent = 100. * $st{"line_reached"} / $st{"line_count"};

			printf OUT "    <td>%.1f%%</td>\n", $percent;
		} else {
			print OUT "    <td>N. A.</td>\n";
		}
	}
	if ($stats{"line_count"}) {
		$percent = 100. * $stats{"line_reached"} / $stats{"line_count"};

		printf OUT "    <td>%.1f%%</td>\n", $percent;
	} else {
		print OUT "    <td>N. A.</td>\n";
	}
	print OUT "    <td>" . $stats{"line_count"} . "</td>\n";

	# If there are more than one tests per file, report them
	my $total = scalar(keys %test_names);
	if ($total > 1) {
		print OUT "  </tr><tr>\n";
		print OUT "    <td><b>Number of tests</b></td>\n";
		foreach my $f (@info_files) {
			my $count = scalar(keys %{$report{$f}{"test_names"}});

			if ($count == 0) {
				print OUT "    <td $red>$count</td>\n";
			} elsif ($count < $total) {
				print OUT "    <td $yellow>$count</td>\n";
			} else {
				print OUT "    <td $green>$count</td>\n";
			}
		}
		print OUT "    <td $green\>$total</td>\n";

	}
	print OUT "  </tr>\n</table><p/>\n\n";

	# Print function diff
	print OUT "  <h2>Differences on function coverage</h2>\n";
	print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n  <tr>\n";
	print OUT "    <th></th>\n";
	print OUT "    <th>Common to all</th>\n";
	foreach my $f (@info_files) {
		print OUT "    <th>$f</th>\n";
	}
	print OUT "    <th>TOTAL</th>\n";

	print OUT "  </tr><tr>\n";
	print OUT "    <td>#Functions per category</td>\n";

	printf OUT "    <td>%d</td>\n", $common_func_count;

	foreach my $f (@info_files) {
		my %st = %{$report{$f}{"stats"}};
		if ($st{"func_count"}) {
			printf OUT "    <td>%d</td>\n", $st{"func_used"};

		}
	}
	print OUT "    <td>" . $stats{"func_count"} . "</td>\n";

	print OUT "  </tr><tr>\n";
	print OUT "    <td>#functions not in common</td>\n";
	print OUT "    <td></td>\n";
	foreach my $f (@info_files) {
		my %st = %{$report{$f}{"stats"}};
		if ($st{"func_count"}) {
			printf OUT "    <td>%d</td>\n", $report{$f}{"more_func"};

		}
	}
	print OUT "    <td></td>\n";

	print OUT "  </tr><tr>\n";
	print OUT "    <td>Unique functions</td>\n";
	print OUT "    <td></td>\n";
	foreach my $f (@info_files) {
		my %st = %{$report{$f}{"stats"}};
		if ($st{"func_count"}) {
			printf OUT "    <td>%d</td>\n", $report{$f}{"uniq_func"};

		}
	}
	print OUT "    <td></td>\n";

	print OUT "  </tr>\n</table><p/>\n\n";

	# Print the filters applied when generating the report

	if ($filter_str ne "") {
		printf OUT "<p>Filters: %s.</p>\n", $filter_str;
	} else {
		print OUT "<p>(unfiltered results)</p>";
	}

	if ($total > 1) {
		print OUT "<h2>Tests coverage</h2>\n";

		print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n  <tr>\n";
		print OUT "    <th>Test name</th>\n";
		foreach my $f (@info_files) {
			print OUT "    <th>$f</th>\n";
		}

		foreach my $t (sort keys(%test_names)) {
			print OUT "  </tr><tr>\n";
			printf OUT "    <td>%s</td>\n", $t;
			foreach my $f (@info_files) {
				if (%{$report{$f}{"test_names"}}{$t}) {
					print OUT "    <td $green>YES</td>\n";
				} else {
					print OUT "    <td $red>NO</td>\n";
				}
			}
		}
		print OUT "</tr></table>\n";
	}


	# Generates a table containing per-function detailed data

	print OUT "<h2>Functions coverage</h2>\n";
	print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n  <tr>\n";
	print OUT "    <th>Function</th>\n";
	print OUT "    <th>Used?</th>\n";
	foreach my $f (@info_files) {
		print OUT "    <th>$f</th>\n";
	}
	print OUT "    <th>File</th>\n";

	foreach my $func (sort keys(%all_func)) {
		my @keys = sort keys(%{$all_func{$func}});
		foreach my $file (@keys) {
			print OUT "  </tr><tr>\n";
			print OUT "    <td>$func</td>\n";
			if ($used_func{$func}->{$file}) {
				print OUT "    <td $green>YES</td>\n";
			} else {
				print OUT "    <td $red>NO</td>\n";
			}
			foreach my $f (@info_files) {
				if ($report{$f}{"used_func"}{$func}->{$file}) {
					print OUT "    <td $green>YES</td>\n";
				} else {
					print OUT "    <td $red>NO</td>\n";
				}
			}
			$file =~ s,$prefix,linux/,;
			print OUT "    <td>$file</td>\n";
		}
	}
	print OUT "</tr></table>\n";

	print OUT "$epilog</body>\n";

	# Close the file and exit

	close OUT;
}

sub check_source_branches()
{
	foreach my $source (sort keys(%all_branch)) {
		next if (!$used_source{$source});
		next if (is_file_excluded($source));

		open IN, "$source_dir/$source" || die "File $source_dir/$source not found. Can't check branches\n";
		my @lines = <IN>;
		close IN;

		foreach my $where (sort keys %{$all_branch{$source}}) {
			my $taken = $all_branch{$source}{$where}{count};
			next if ($taken > 0);

			next if !($where =~ m/^(-?\d+),(-?\d+),(-?\d+)/);

			my ($ln, $block, $branch, $ctx_lines);
			$ln = $1;
			$block = $2;
			$branch = $3;

			if ($ln > $#lines) {
				die "$source:$ln line is bigger than the number of lines at the file ($#lines lines)\n";
				return;
			}

			my $func_ln;
			my $func = $all_branch{$source}{$where}{func};
			if ($func) {
				$func_ln = $all_func{$func}{$source}->{ln};
			}

			print "Branch $branch on $source:$ln";
			print ", function: $func" if ($func);
			print " not taken:\n";

			if ($func_ln) {
				$ctx_lines = $ln - $func_ln;
			} else {
				$ctx_lines = 10;
			}

			# Search for up to $ctx_lines before the occurrence
			my $context = "";
			my $has_if;
			$ln-- if ($ln > 0);
			my $delim = "->";
			for (my $if_ln = $ln; $if_ln >= 0 && (($ln - $if_ln) < $ctx_lines); $if_ln--) {
				my $cur_ln = $if_ln + 1;
				$context = "$cur_ln$delim\t$lines[$if_ln]" . $context;
				$delim = "";
				if ($lines[$if_ln] =~ m/(\bif\b|#if)/) {
					$has_if = 1;
					last;
				}
			}
			if ($has_if) {
				print $context;
			} else {
				my $cur_ln = $ln + 1;
				print "$cur_ln->\t$lines[$ln]";
			}
		}
	}
}

#
# Argument handling
#

my $print_used;
my $print_unused;
my $stat;
my $output_file;
my $help;
my $man;
my $func_filters;
my $src_filters;
my $branch_filters;
my $show_files;
my $show_lines;
my $only_i915;
my $only_xe;
my $only_drm;
my $check_branches;

GetOptions(
	"print-coverage|print_coverage|print|p" => \$print_used,
	"print-unused|u" => \$print_unused,
	"stat|statistics" => \$stat,
	"output|o=s" => \$output_file,
	"source-dir|source_dir=s" => \$source_dir,
	"verbose|v" => \$verbose,
	"ignore-unused|ignore_unused" => \$ignore_unused,
	"only-i915|only_i915" => \$only_i915,
	"only-xe|only_xe" => \$only_xe,
	"only-drm|only_drm" => \$only_drm,
	"func-filters|f=s" => \$func_filters,
	"include-func=s" => \@func_include_regexes,
	"exclude-func=s" => \@func_exclude_regexes,
	"branch-filters|f=s" => \$branch_filters,
	"include-branch=s" => \@branch_include_regexes,
	"exclude-branch=s" => \@branch_exclude_regexes,
	"source-filters|S=s" => \$src_filters,
	"include-source=s" => \@src_include_regexes,
	"exclude-source=s" => \@src_exclude_regexes,
	"ignore-lines-without-functions!" => \$ignore_lines_without_functions,
	"ignore-branches-on-headers!" => \$ignore_branches_on_headers,
	"show-files|show_files" => \$show_files,
	"show-lines|show_lines" => \$show_lines,
	"report|r=s" => \$gen_report,
	"check-branches|check_branches" => \$check_branches,
	"css-file|css|c=s" => \$css_file,
	"title|t=s" => \$title,
	"html-prolog|prolog=s" => \$html_prolog,
	"html-epilog|epilog=s" => \$html_epilog,
	"help" => \$help,
	"man" => \$man,
) or pod2usage(2);

pod2usage(-verbose => 2) if $man;
pod2usage(1) if $help;

if ($#ARGV < 0) {
	print "$0: no input files\n";
	pod2usage(1);
}

# At least one action should be specified
pod2usage(1) if (!$print_used && !$output_file && !$stat && !$print_unused && !$gen_report && !$check_branches);

pod2usage(1) if ($gen_report && ($print_used || $output_file || $stat || $print_unused));

my $filter_str = "";
my $has_filter;
my $str;

if ($only_i915) {
	# Please keep in sync with the documentation
	push @src_exclude_regexes, "selftest";
	push @src_include_regexes, "drm/i915";
	push @src_include_regexes, "drm/ttm";
	push @src_include_regexes, "drm/vgem";
}

if ($only_xe) {
	# Please keep in sync with the documentation
	push @src_exclude_regexes, "selftest";
	push @src_include_regexes, "drm/xe";
#	push @src_include_regexes, "drm/ttm";
#	push @src_include_regexes, "drm/vgem";
}

if ($only_drm) {
	# Please keep in sync with the documentation
	push @src_exclude_regexes, "trace.*\.h\$";
	push @src_exclude_regexes, "^/drm/";
}

$str = open_filter_file($func_filters, \@func_include_regexes, \@func_exclude_regexes);
if ($str) {
	$filter_str .= "," if ($filter_str ne "");
	$filter_str .= " function regex ($str)";
	$has_filter = 1;
}

$str = open_filter_file($src_filters, \@src_include_regexes, \@src_exclude_regexes);
if ($str) {
	$filter_str .= "," if ($filter_str ne "");
	$filter_str .= " source regex ($str)";
	$has_filter = 1;
}

$str = open_filter_file($branch_filters, \@branch_include_regexes, \@branch_exclude_regexes);
if ($str) {
	$filter_str .= "," if ($filter_str ne "");
	$filter_str .= " branch regex ($str)";
	$has_filter = 1;
	$has_branch_filter = 1;
}

$ignore_unused = 1 if (@func_include_regexes || @func_exclude_regexes);

if ($ignore_unused) {
	$filter_str .= "," if ($filter_str ne "");
	$filter_str .= " ignored source files where none of its code ran";
	$has_filter = 1;
}

foreach my $f (@ARGV) {
	if ($f =~ /\.json(\.gz)?$/) {
		read_json($f);
	} else {
		read_info($f);
	}

	if ($gen_report) {
		$f =~ s,.*/,,;
		$f =~ s/\.gz$//;
		$f =~ s/\.info$//;
		$f =~ s/\.json$//;

		gen_stats();

		$report{$f}{"stats"} = { %stats };
		$report{$f}{"all_func"} = { %all_func };
		$report{$f}{"used_func"} = { %used_func };
		$report{$f}{"all_branch"} = { %all_branch };
		$report{$f}{"all_line"} = { %all_line };
		$report{$f}{"used_source"} = { %used_source };
		$report{$f}{"files"} = { %files };
		$report{$f}{"test_names"} = { %test_names };

		%used_func = ();
		%all_func = ();
		%all_branch = ();
		%all_line = ();
		%used_source = ();
		%files = ();
		%test_names = ();
	}
}

$filter_str =~ s/(.*),/$1 and/ if ($filter_str ne "");

if ($gen_report) {
	generate_report($filter_str);
	exit 0;
}

gen_stats();


if ($check_branches) {
	check_source_branches();
}

die "Nothing counted. Wrong input files?" if (!$stats{"all_files"});

print_code_coverage($print_used, $print_unused, $show_lines);

print_summary() if ($stat);

if ($has_filter) {
	my $percent = 100. * $stats{"used_files"} / $stats{"all_files"};

	if (!$can_filter_lines) {
		printf "Warning......: Function filters won't work with lines/branches\n";
	}

	printf "Filters......:%s.\n", $filter_str;
	printf "Source files.: %.2f%% (%d of %d total)",
		$percent, $stats{"used_files"}, $stats{"all_files"};

	if ($stats{"used_files"} != $stats{"filtered_files"}) {
		my $percent_filtered = 100. * $stats{"used_files"} / $stats{"filtered_files"};

		printf ", %.2f%% (%d of %d filtered)",
			$percent_filtered, $stats{"used_files"}, $stats{"filtered_files"};
	}
	print "\n";
} else {
	printf "Source files: %d\n", scalar keys(%files) if($stat);
}

my $ntests=scalar(%test_names);
printf "Number of tests: %d\n", $ntests if ($stat && $ntests > 1);

if ($show_files) {
	for my $f(sort keys %used_source) {
		print "\t$f\n";
	}
}

if ($output_file) {
	if ($output_file =~ /.json(.gz)?$/) {
		write_json_file($output_file);
	} else {
		write_info_file($output_file);
	}
}

__END__

=head1 NAME

Parses lcov data from .info files.

=head1 SYNOPSIS

code_cov_parse_info <options> [input file(s)]

At least one of the output options should be used, e g.
B<--stat>, B<--print>, B<--print-unused>, B<--report> and/or B<--output>.

Also, B<--report> can't be used together with other output options.

=head1 OPTIONS

=over 8

=item B<--stat> or B<--statistics>

Prints code coverage statistics.

It displays function, line, branch and file coverage percentage.

It also reports when one or more of the filtering parameters are used.

The statistics report is affected by the applied filters.

=item B<--print-coverage> or B<--print_coverage> or B<--print> or B<-p>

Prints the functions that were executed in runtime and how many times
they were reached.

The function coverage report is affected by the applied filters.

=item B<--print-unused> or B<-u>

Prints the functions that were never reached.

The function coverage report is affected by the applied filters.

=item B<--report>  B<[output file]> or B<-r>  B<[output file]>

Generates an html report containing per-test and total statistics.

The function coverage report is affected by the applied filters.

=item B<--css-file> B<[css file]> or B<--css> B<[css file]> or B<-c> B<[css file]

Adds an optional css file to the html report.
Used only with B<--report>.

=item B<--title> B<[title] or B<-t> B<[title]

If used with B<--report>, it defines the title for the for the html report.

If used with B<--output>, it replaces the test names with the title. This
is useful when merging reports from multiple tests into a summarized file.
If not used, the B<[output file]> will contain all test names on its
beginning.

Used with B<--report> AND B<--output>.

=item B<--html-prolog> B<[html file] or B<--prolog> B<[html file]

Adds a prolog at the beginning of the body of the html report.
Used only with B<--report>.

=item B<--html-epilog>  B<[html file] or B<--epilog>  B<[html file]

Adds an epilog before the end of the body of the html report.
Used only with B<--report>.

=item B<--show-lines> or B<--show_lines>

When printing per-function code coverage data, always output the source
file and the line number where the function is defined.

=item B<--output> B<[output file]> or B<-o> B<[output file]>

Produce an output file merging all input files.

The generated output file is affected by the applied filters.

=item B<--source-dir> or B<--source_dir>

Sets the source directory baseline. This is used together with other
options that require to parse the source files (currently, only
B<--check-branches).

=item B<--only-drm> or B<--only_drm>

Filters out includes outside the DRM subsystem, plus trace files.
E. g. it will exclude *.h files that match the following regular expressions:

	- .*trace.*\.h$

And *.h files that don't match:

	- /drm/

(e. g. macros and other code outside DRM subsystem)

=item B<--only-i915> or B<--only_i915>

Filters out C files and headers outside drm core and drm/i915.

E. g. code coverage results will include only the files that that match
the following regular expressions:

	- drm/i915/
	- drm/ttm
	- drm/vgem

Excluding files that match:

	- selftest

=item B<--only-xe> or B<--only_xe>

Filters out C files and headers outside drm core and drm/i915.

E. g. code coverage results will include only the files that that match
the following regular expressions:

	- drm/xe/

Excluding files that match:

	- selftest

=item B<--func-filters>  B<[filter's file]> or B<-f>  B<[filter's file]>

Use a file containing regular expressions (regex) to filter functions.

Each line at B<[filter's file]> may contain a new regex:

=over 4

- Blank lines and lines starting with B<#> will be ignored;

- Each line of the file will be handled as a new regex;

- If B<+regex> is used, the filter will include B<regex> to the matches;

- If B<-regex> is used, the filter will exclude B<regex> from the matches;

- If the line doesn't start with neither B<+> nor B<->, containing just
  B<regex>, the filter will include B<regex> to the matches.

- Any whitespace/tab before or after B<regex> will be ignored.

=back

Include regexes are handled first, as they can override an exclude regex.

When just include regexes are used, any functions that don't match the
include regular expressions from the B<[filter's file]> will be ignored.

Please notice that, when this filter is used, B<--ignore-unused> will be
automaticaly enabled, as the final goal is to report per-function usage.

=item B<--include-func> B<regex>

Include B<regex> to the function filter. Can be used multiple times.

When used together with B<--func-filters> or B<--exclude-func>, regexes
here are handled first.

Please notice that, when this filter is used, B<--ignore-unused> will be
automaticaly enabled, as the final goal is to report per-function usage.

=item B<--exclude-func> B<regex>

Include B<regex> to the function filter. Can be used multiple times.

Please notice that, when this filter is used, B<--ignore-unused> will be
automaticaly enabled, as the final goal is to report per-function usage.

=item B<--branch-filters>  B<[filter's file]> or B<-f>  B<[filter's file]>

Use a file containing regular expressions (regex) to filter branches.

Each line at B<[filter's file]> may contain a new regex:

=over 4

- Blank lines and lines starting with B<#> will be ignored;

- Each line of the file will be handled as a new regex;

- If B<+regex> is used, the filter will include B<regex> to the matches;

- If B<-regex> is used, the filter will exclude B<regex> from the matches;

- If the line doesn't start with neither B<+> nor B<->, containing just
  B<regex>, the filter will include B<regex> to the matches.

- Any whitespace/tab before or after B<regex> will be ignored.

=back

Include regexes are handled first, as they can override an exclude regex.

When just include regexes are used, any branches that don't match the
include regular expressions from the B<[filter's file]> will be ignored.

=item B<--include-branch> B<regex>

Include B<regex> to the branch filter. Can be used multiple times.

When used together with B<--branch-filters> or B<--exclude-branch>, regexes
here are handled first.

Please notice that, when this filter is used, B<--ignore-unused> will be
automaticaly enabled, as the final goal is to report per-branch usage.

=item B<--exclude-branch> B<regex>

Include B<regex> to the branchtion filter. Can be used multiple times.

Please notice that, when this filter is used, B<--ignore-unused> will be
automaticaly enabled, as the final goal is to report per-branch usage.

=item B<--source-filters>  B<[filter's file]> or B<-S>  B<[filter's file]>

Use a file containing regular expressions to filter source files.

Each line of the file will be handled as a new regular expressions.
Blank lines and lines starting with B<#> will be ignored.

Each line at B<[filter's file]> may contain a new regex:

=over 4

- Blank lines and lines starting with B<#> will be ignored;

- Each line of the file will be handled as a new regex;

- If B<+regex> is used, the filter will include B<regex> to the matches;

- If B<-regex> is used, the filter will exclude B<regex> from the matches;

- If the line doesn't start with neither B<+> nor B<->, containing just
  B<regex>, the filter will include B<regex> to the matches.

- Any whitespace/tab before or after B<regex> will be ignored.

=back

Include regexes are handled first, as they can override an exclude regex.

=item B<--include-src> B<regex>

Include B<regex> to the sources filter. Can be used multiple times.

When used together with B<--src-filters> and B<--exclude-src>, regexes
here are handled first.

=item B<--exclude-src> B<regex>

Include B<regex> to the sources filter. Can be used multiple times.

=item B<--ignore-unused> or B<--ignore_unused>

Filters out unused C files and headers from the code coverage results.

Sometimes, it is desired to ignore files where none of the functions on it
were tested.

The rationale is that such files may contain platform-specific drivers
and code that will never be used, so, placing them will just bloat the
report and decrease the code coverage statistics.

This option is automaticaly enabled when B<--func-filters> is used.

=item B<--ignore-lines-without-functions>

This option works only when the input file is in JSON format.

Basically, include files may contain several lines of codes that aren't
assigned to any functions inside a source file. This is a common behavior
for macros and inlined functions inside headers.

When this option is selected, the branches stat won't contain any such code.

Please notice that this is enabled by default.

Use B<--no-ignore-lines-without-functions> to disable it.

=item B<--no-ignore-lines-without-functions>

Disables filtering out branches that are not associated with any functions
inside the source file, but were imported via includes.

See B<--ignore-lines-without-functions> for more details.

=item B<--ignore-branches-on-headers>

Branches on header files are really tricky to parse, as they depend
on how gcc optimizes the output code. That's specially hard to use on
Linux Kernel, as there are lots of complex macros that can be optimized
on different ways. There are even some cases where the same macro sometimes
have zero branches, while on other cases it can contain dozen ones.

When this option is selected, all branches inside header files will be
ignored.

Please notice that this is enabled by default.

Use B<--no-ignore-branches-on-headers> to disable this filter, preserving
data from all branches.

=item B<--no-ignore-branches-on-headers>

Disables filtering out branches that are inside header files.

See B<--ignore-branches-on-headers> for more details.

=back

=item B<--show-files> or B<--show_files>

Shows the list of files that were used to produce the code coverage
results.

=item B<--check-branches> or B<--check_branches>

Checks at the Linux Kernel source files what's the contents of the
branches that weren't taken. The directory should match what's
stored inside the info files.

=item B<--verbose> or B<-v>

Prints the name of each parsed file.

=item B<--help>

Print a brief help message and exits.

=item B<--man>

Prints the manual page and exits.

=back

=head1 BUGS

Report bugs to Mauro Carvalho Chehab <mauro.chehab@intel.com>

=head1 COPYRIGHT

Copyright (c) 2022 Intel Corporation

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
=cut
