kernel-doc: check for extra kernel-doc notations

Add functionality to check for function parameters or structure (or
union/typedef/enum) field members that are described in kernel-doc but
are not part of the expected (declared) parameters or structure.
These generate warnings that are called "Excess" descriptions.

Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com>
Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
diff --git a/scripts/kernel-doc b/scripts/kernel-doc
index d27aad7..8bb83a1 100755
--- a/scripts/kernel-doc
+++ b/scripts/kernel-doc
@@ -289,6 +289,8 @@
 my @parameterlist;
 my %sections;
 my @sectionlist;
+my $sectcheck;
+my $struct_actual;
 
 my $contents = "";
 my $section_default = "Description";	# default section
@@ -378,10 +380,12 @@
 #	print STDERR "parameter def '$1' = '$contents'\n";
 	$name = $1;
 	$parameterdescs{$name} = $contents;
+	$sectcheck = $sectcheck . $name . " ";
     } elsif ($name eq "@\.\.\.") {
 #	print STDERR "parameter def '...' = '$contents'\n";
 	$name = "...";
 	$parameterdescs{$name} = $contents;
+	$sectcheck = $sectcheck . $name . " ";
     } else {
 #	print STDERR "other section '$name' = '$contents'\n";
 	if (defined($sections{$name}) && ($sections{$name} ne "")) {
@@ -1405,21 +1409,25 @@
 sub dump_struct($$) {
     my $x = shift;
     my $file = shift;
+    my $nested;
 
     if ($x =~/(struct|union)\s+(\w+)\s*{(.*)}/) {
 	$declaration_name = $2;
 	my $members = $3;
 
 	# ignore embedded structs or unions
-	$members =~ s/{.*}//g;
+	$members =~ s/({.*})//g;
+	$nested = $1;
 
 	# ignore members marked private:
 	$members =~ s/\/\*.*?private:.*?public:.*?\*\///gos;
 	$members =~ s/\/\*.*?private:.*//gos;
 	# strip comments:
 	$members =~ s/\/\*.*?\*\///gos;
+	$nested =~ s/\/\*.*?\*\///gos;
 
 	create_parameterlist($members, ';', $file);
+	check_sections($file, $declaration_name, "struct", $sectcheck, $struct_actual, $nested);
 
 	output_declaration($declaration_name,
 			   'struct',
@@ -1505,6 +1513,14 @@
     }
 }
 
+sub save_struct_actual($) {
+    my $actual = shift;
+
+    # strip all spaces from the actual param so that it looks like one string item
+    $actual =~ s/\s*//g;
+    $struct_actual = $struct_actual . $actual . " ";
+}
+
 sub create_parameterlist($$$) {
     my $args = shift;
     my $splitter = shift;
@@ -1537,6 +1553,7 @@
 	    $param = $1;
 	    $type = $arg;
 	    $type =~ s/([^\(]+\(\*?)\s*$param/$1/;
+	    save_struct_actual($param);
 	    push_parameter($param, $type, $file);
 	} elsif ($arg) {
 	    $arg =~ s/\s*:\s*/:/g;
@@ -1561,14 +1578,17 @@
 
 	    foreach $param (@args) {
 		if ($param =~ m/^(\*+)\s*(.*)/) {
+		    save_struct_actual($2);
 		    push_parameter($2, "$type $1", $file);
 		}
 		elsif ($param =~ m/(.*?):(\d+)/) {
 		    if ($type ne "") { # skip unnamed bit-fields
+			save_struct_actual($1);
 			push_parameter($1, "$type:$2", $file)
 		    }
 		}
 		else {
+		    save_struct_actual($param);
 		    push_parameter($param, $type, $file);
 		}
 	    }
@@ -1634,6 +1654,46 @@
 	$parametertypes{$param} = $type;
 }
 
+sub check_sections($$$$$$) {
+	my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck, $nested) = @_;
+	my @sects = split ' ', $sectcheck;
+	my @prms = split ' ', $prmscheck;
+	my $err;
+	my ($px, $sx);
+	my $prm_clean;		# strip trailing "[array size]" and/or beginning "*"
+
+	foreach $sx (0 .. $#sects) {
+		$err = 1;
+		foreach $px (0 .. $#prms) {
+			$prm_clean = $prms[$px];
+			$prm_clean =~ s/\[.*\]//;
+			$prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//;
+			##$prm_clean =~ s/^\**//;
+			if ($prm_clean eq $sects[$sx]) {
+				$err = 0;
+				last;
+			}
+		}
+		if ($err) {
+			if ($decl_type eq "function") {
+				print STDERR "Warning(${file}:$.): " .
+					"Excess function parameter " .
+					"'$sects[$sx]' " .
+					"description in '$decl_name'\n";
+				++$warnings;
+			} else {
+				if ($nested !~ m/\Q$sects[$sx]\E/) {
+				    print STDERR "Warning(${file}:$.): " .
+					"Excess struct/union/enum/typedef member " .
+					"'$sects[$sx]' " .
+					"description in '$decl_name'\n";
+				    ++$warnings;
+				}
+			}
+		}
+	}
+}
+
 ##
 # takes a function prototype and the name of the current file being
 # processed and spits out all the details stored in the global
@@ -1699,6 +1759,9 @@
 	return;
     }
 
+	my $prms = join " ", @parameterlist;
+	check_sections($file, $declaration_name, "function", $sectcheck, $prms, "");
+
     output_declaration($declaration_name,
 		       'function',
 		       {'function' => $declaration_name,
@@ -1757,6 +1820,8 @@
     @parameterlist = ();
     %sections = ();
     @sectionlist = ();
+    $sectcheck = "";
+    $struct_actual = "";
     $prototype = "";
 
     $state = 0;