#!/usr/bin/perl -w
#
# add2db.pl dbfile test < smatch.output
#
# Copyright 2003-2009 Michael Stefaniuc
#
# Takes the output of the smatch scripts and checks/adds them to the db file.
# Format of the db file:
#   # comment
#   filename:fingerprint:linenr:test:status:comment
# where:
#   filename = path of file relative to the root of the wine repository
#   fingerprint = smatch's finger print, output of get_func_pos()
#   linenr = linenr's where the error is produced
#   test = test that produced this. the subtest is added after a "/" to the test
#   status = status of the bug. one of NEW, UNKNOWN, BUG, NOTABUG, PATCH, FIXED
#   comment = anything

use strict;

# Variables
############

# Path to the root of the wine tree. Used to decide if it's a wine error.
my $wine_path = "/j/s";
# global hash with all the entries
my %globaldb = ();
# database file
my $dbfile = "";
# test from which this output is
my $running_test = "";
# tests for which to import the comments too. Actual value is not important
my %has_comment = (
	    "redundant_null_check" => '',
	    "unfree-wine" => '',
	    "crosscalls_WtoA" => '',
	    "file_handles" => '',
	    );
my $comment_f = 0;
# statistics
my %stats = (
	    "new" => 0,		# new entries
	    "old" => 0,		# old entries still valid
	    "notfound" => 0,	# entries that couldn't be found in the
	    			# smatch output
	    "other" => 0,	# entries from other tests
	    );
my $date = `date +%Y%m%d`;
chomp($date);


# Functions
############
sub usage {
    print "Usage: add2db.pl dbfile test < smatch.output\n";
}

sub parse_dbfile() {
    open(my $fd, "<$dbfile") or return;
    while(<$fd>) {
	m/^#/ and do {next;};
	m/^\s*$/ and do {next;};
	if (tr/:/:/ < 5) {
	    print(STDERR "WARN: couldn't parse in db file\n\t$_");
	    next;
	}
	my %rec = ();
	chomp;
	my @line = split(/:/, $_);
	$rec{"filename"} = shift(@line);
	$rec{"fingerprint"} = shift(@line);
	$rec{"linenr"} = shift(@line);
	$rec{"test"} = shift(@line);
	$rec{"status"} = shift(@line);
	if (defined($line[0])) {
	    $rec{"comment"} = shift(@line);
	} else {
	    $rec{"comment"} = "";
	}
	# the comment may have contained a ':'
	while (defined($line[0])) {
	    $rec{"comment"} .= ':' . shift(@line);
	}
	my $key = join(":", $rec{"filename"}, $rec{"fingerprint"}, $rec{"test"});
	$globaldb{$key} = \%rec;
    }
}


sub parse_smatch_output() {
    while (<>) {
	s/^$wine_path\/// and do {
	    m/^(\S+) ((?:\d+ )+)(\w+\(\d+\)) (.*)$/ and do {
		my $filename = $1;
		my $linenr = $2;
		chop($linenr);
		$linenr =~ tr/ /-/;
		my $fingerprint = $3;
		my $key = join(":", $filename, $fingerprint, $running_test);
		my $comment = $4;
		if ($comment_f) {
		    $comment = "";
		}

		if (!exists($globaldb{$key})) {
		    my %rec = (
			    "filename" => $filename,
			    "fingerprint" => $fingerprint,
			    "linenr" => $linenr,
			    "test" => $running_test,
			    "status" => "NEW",
			    "comment" => "<!--new:$date-->" . $comment,
			    "found" => 1,
			    );
		    $stats{"new"}++;
		    $globaldb{$key} = \%rec;
		} else {
		    ${$globaldb{$key}}{"linenr"} = $linenr;
		    if (${$globaldb{$key}}{"status"} eq "FIXED") {
			${$globaldb{$key}}{"status"} = "NEW";
			${$globaldb{$key}}{"comment"} = "reopened";
			$stats{"new"}++;
		    } else {
			$stats{"old"}++;
		    }
		    ${$globaldb{$key}}{"found"} = 1;
		    ${$globaldb{$key}}{"comment"} =~ s/\<!--\??fix(ed)?[?:]\d*--\>//;
		}
		next;
	    };
	    # couldn't parse
	    print(STDERR "WARN: couldn't parse\n\t$_");
	    next;
	};
	# error seems to be not produced by a wine file
	if (m/^\S+\.tab\.c .*var_decl\(yy[sv]sp\)/) {
	    # Stupid outogenerated bison code not worth a warning
	    next;
	}
	print(STDERR "WARN: ignoring non wine error\n\t$_");
    }
}

sub dbsort {
    my $recA = $globaldb{$a};
    my $recB = $globaldb{$b};
    # first sort by filename
    $$recA{"filename"} cmp $$recB{"filename"}
    # numericaly reversed by the second line number
    or do {
	my @lineA = split("-", $$recA{"linenr"});
	my @lineB = split("-", $$recB{"linenr"});
	if (defined($lineA[1])) {
	    shift @lineA;
	}
	if (defined($lineB[1])) {
	    shift @lineB;
	}
	$lineB[0] <=> $lineA[0];
    }
}

sub write_db() {
    open(my $fd, ">$dbfile") or die "ERR: couldn't write to db file '$dbfile'";
    print($fd "# filename:fingerprint:linenr:test:status:comment\n");
    foreach my $entry (sort dbsort keys(%globaldb)) {
	my $rec = $globaldb{$entry};
	if ($$rec{"test"} =~ m/^$running_test$/) {
	    if ((!exists($$rec{"found"}) or !$$rec{"found"}) and ($$rec{"status"} !~ m/FIXED/)){
		$$rec{"comment"} = "<!--?fixed?$date-->" . $$rec{"comment"};
		$stats{"notfound"}++;
	    }
	} else {
	    $stats{"other"}++;
	}
	printf($fd "%s:%s:%s:%s:%s:%s\n", $$rec{"filename"},
		$$rec{"fingerprint"}, $$rec{"linenr"}, $$rec{"test"},
		$$rec{"status"}, $$rec{"comment"});
    }
}

sub print_stats() {
    print("Test \"$running_test\" entries:\n");
    printf("          new: %3d\n", $stats{"new"});
    printf("          old: %3d\n", $stats{"old"});
    printf("    not found: %3d\n", $stats{"notfound"});
    printf("Other entries: %3d\n", $stats{"other"});
    printf("Total entries: %3d\n", $stats{"new"} + $stats{"old"} + $stats{"notfound"} + $stats{"other"});
}


# Main
#######
if ($#ARGV + 1 != 2) {
    usage();
    exit 1;
}
$dbfile = shift(@ARGV);
$running_test = shift(@ARGV);
my $base_test = $running_test;
$base_test =~ s,/.+$,,;
if (!exists($has_comment{$base_test})) {
    $comment_f = 1;
}

parse_dbfile();
parse_smatch_output();
write_db();
print_stats();
