Add ex_data to EVP_PKEY.
[openssl.git] / util / find-doc-nits
index c508e242fe8cde9f79ec3cf935c7573d177a32cf..6eea6ad4b3065c740b03b562437f0a9ebe32a58f 100755 (executable)
@@ -79,7 +79,6 @@ die "Need one of -[cdehlnouv] flags.\n"
 
 my $temp = '/tmp/docnits.txt';
 my $OUT;
-my %public;
 my $status = 0;
 
 my @sections = ( 'man1', 'man3', 'man5', 'man7' );
@@ -89,7 +88,17 @@ my %mandatory_sections = (
     3   => [ 'SYNOPSIS', 'RETURN VALUES' ],
     5   => [ ],
     7   => [ ]
-);
+                         );
+
+# Symbols that we ignored.
+# They are internal macros that we currently don't document
+my $ignored = qr/(?| ^i2d_
+                 |   ^d2i_
+                 |   ^DEPRECATEDIN
+                 |   \Q_fnsig(3)\E$
+                 |   ^IMPLEMENT_
+                 |   ^_?DECLARE_
+                 )/x;
 
 # Collect all POD files, both internal and public, and regardless of location
 # We collect them in a hash table with each file being a key, so we can attach
@@ -266,13 +275,13 @@ sub name_synopsis {
     return unless $contents =~ /=head1 NAME(.*)=head1 SYNOPSIS/ms;
     my $tmp = $1;
     $tmp =~ tr/\n/ /;
-    err($id, "trailing comma before - in NAME")
+    err($id, "Trailing comma before - in NAME")
         if $tmp =~ /, *-/;
     $tmp =~ s/ -.*//g;
     err($id, "POD markup among the names in NAME")
         if $tmp =~ /[<>]/;
     $tmp =~ s/  */ /g;
-    err($id, "missing comma in NAME")
+    err($id, "Missing comma in NAME")
         if $tmp =~ /[^,] /;
 
     my $dirname = dirname($filename);
@@ -284,7 +293,7 @@ sub name_synopsis {
     foreach my $n ( split ',', $tmp ) {
         $n =~ s/^\s+//;
         $n =~ s/\s+$//;
-        err($id, "the name '$n' contains white-space")
+        err($id, "The name '$n' contains white-space")
             if $n =~ /\s/;
         $names{$n} = 1;
         $foundfilename++ if $n eq $simplename;
@@ -293,17 +302,11 @@ sub name_synopsis {
                    files(TAGS => [ 'manual', $section ]) )
                  && $n ne $simplename );
     }
-    err($id, "the following exist as other .pod files:",
+    err($id, "The following exist as other .pod files:",
          sort keys %foundfilenames)
         if %foundfilenames;
     err($id, "$simplename (filename) missing from NAME section")
         unless $foundfilename;
-    if ( $filename !~ /internal/ ) {
-        foreach my $n ( keys %names ) {
-            err($id, "$n is not public")
-                if !defined $public{$n};
-        }
-    }
 
     # Find all functions in SYNOPSIS
     return unless $contents =~ /=head1 SYNOPSIS(.*)=head1 DESCRIPTION/ms;
@@ -318,7 +321,7 @@ sub name_synopsis {
         if ( $line =~ /typedef.*\(\*\S+\)\s+\(/ ) {
             # a callback function with whitespace before the argument list:
             # typedef ... (*NAME) (...
-            err($id, "function typedef has space before arg list: $line");
+            err($id, "Function typedef has space before arg list: $line");
         }
         if ( $line =~ /env (\S*)=/ ) {
             # environment variable env NAME=...
@@ -350,7 +353,7 @@ sub name_synopsis {
         $names{$sym} = 2;
 
         # Do some sanity checks on the prototype.
-        err($id, "prototype missing spaces around commas: $line")
+        err($id, "Prototype missing spaces around commas: $line")
             if $is_prototype && $line =~ /[a-z0-9],[^ ]/;
     }
 
@@ -386,20 +389,20 @@ sub check_head_style {
     foreach my $line ( split /\n+/, $contents ) {
         next unless $line =~ /^=head/;
         if ( $line =~ /head1/ ) {
-            err($id, "duplicate section $line")
+            err($id, "Duplicate section $line")
                 if defined $head1{$line};
             $head1{$line} = 1;
             %subheads = ();
         } else {
-            err($id, "duplicate subsection $line")
+            err($id, "Duplicate subsection $line")
                 if defined $subheads{$line};
             $subheads{$line} = 1;
         }
-        err($id, "period in =head")
+        err($id, "Period in =head")
             if $line =~ /\.[^\w]/ or $line =~ /\.$/;
         err($id, "not all uppercase in =head1")
             if $line =~ /head1.*[a-z]/;
-        err($id, "all uppercase in subhead")
+        err($id, "All uppercase in subhead")
             if $line =~ /head[234][ A-Z0-9]+$/;
     }
 }
@@ -537,7 +540,7 @@ sub functionname_check {
         $unmarked =~ s/[BIL]<|>//msg;
 
         err($id, "Malformed symbol: $symbol")
-            unless $symbol =~ /^B<.*>$/ && $unmarked =~ /^${symbol_re}$/
+            unless $symbol =~ /^B<.*?>$/ && $unmarked =~ /^${symbol_re}$/
     }
 
     # We can't do the kind of collecting coolness that option_check()
@@ -596,10 +599,10 @@ sub wording {
         # Sigh, trademark
         next if $k eq 'file system'
             and $contents =~ /Microsoft Encrypted File System/;
-        err($id, "found '$k' should use '$preferred_words{$k}'")
+        err($id, "Found '$k' should use '$preferred_words{$k}'")
             if $contents =~ /\b\Q$k\E\b/i;
     }
-    err($id, "found 'epoch' should use 'Epoch'")
+    err($id, "Found 'epoch' should use 'Epoch'")
         if $contents =~ /\bepoch\b/;
     if ( $id =~ m@man1/@ ) {
         err($id, "found 'tool' in NAME, should use 'command'")
@@ -612,16 +615,10 @@ sub wording {
 
 # Perform all sorts of nit/error checks on a manpage
 sub check {
-    my $filename = shift;
+    my %podinfo = @_;
+    my $filename = $podinfo{filename};
     my $dirname = basename(dirname($filename));
-
-    my $contents = '';
-    {
-        local $/ = undef;
-        open POD, $filename or die "Couldn't open $filename, $!";
-        $contents = <POD>;
-        close POD;
-    }
+    my $contents = $podinfo{contents};
 
     my $id = "${filename}:1:";
     check_head_style($id, $contents);
@@ -663,7 +660,7 @@ sub check {
             unless $target =~ /^[_[:alpha:]][_[:alnum:]]*$/
     }
 
-    unless ( $contents =~ /=for openssl generic/ ) {
+    unless ( $contents =~ /^=for openssl generic/ms ) {
         if ( $filename =~ m|man3/| ) {
             name_synopsis($id, $filename, $contents);
             functionname_check($id, $filename, $contents);
@@ -674,25 +671,25 @@ sub check {
 
     wording($id, $contents);
 
-    err($id, "doesn't start with =pod")
+    err($id, "Doesn't start with =pod")
         if $contents !~ /^=pod/;
-    err($id, "doesn't end with =cut")
+    err($id, "Doesn't end with =cut")
         if $contents !~ /=cut\n$/;
-    err($id, "more than one cut line.")
+    err($id, "More than one cut line.")
         if $contents =~ /=cut.*=cut/ms;
     err($id, "EXAMPLE not EXAMPLES section.")
         if $contents =~ /=head1 EXAMPLE[^S]/;
     err($id, "WARNING not WARNINGS section.")
         if $contents =~ /=head1 WARNING[^S]/;
-    err($id, "missing copyright")
+    err($id, "Missing copyright")
         if $contents !~ /Copyright .* The OpenSSL Project Authors/;
-    err($id, "copyright not last")
+    err($id, "Copyright not last")
         if $contents =~ /head1 COPYRIGHT.*=head/ms;
     err($id, "head2 in All uppercase")
         if $contents =~ /head2\s+[A-Z ]+\n/;
-    err($id, "extra space after head")
+    err($id, "Extra space after head")
         if $contents =~ /=head\d\s\s+/;
-    err($id, "period in NAME section")
+    err($id, "Period in NAME section")
         if $contents =~ /=head1 NAME.*\.\n.*=head1 SYNOPSIS/ms;
     err($id, "Duplicate $1 in L<>")
         if $contents =~ /L<([^>]*)\|([^>]*)>/ && $1 eq $2;
@@ -708,7 +705,7 @@ sub check {
             my $count = 0;
             foreach my $line ( split /\n+/, $1 ) {
                 if ( $line =~ m@include <openssl/@ ) {
-                    err($id, "has multiple includes")
+                    err($id, "Has multiple includes")
                         if ++$count == 2;
                 } else {
                     $count = 0;
@@ -735,15 +732,43 @@ sub check {
     $section = $1 if $dirname =~ /man([1-9])/;
 
     foreach ( (@{$mandatory_sections{'*'}}, @{$mandatory_sections{$section}}) ) {
-        err($id, "missing $_ head1 section")
+        err($id, "Missing $_ head1 section")
             if $contents !~ /^=head1\s+${_}\s*$/m;
     }
 }
 
+# Information database ###############################################
+
+# Map of links in each POD file; filename => [ "foo(1)", "bar(3)", ... ]
+my %link_map = ();
+# Map of names in each POD file or from "missing" files; possible values are:
+# If found in a POD files, "name(s)" => filename
+# If found in a "missing" file or external, "name(s)" => ''
+my %name_map = ();
+
+# State of man-page names.
+# %state is affected by loading util/*.num and util/*.syms
+# Values may be one of:
+# 'crypto' : belongs in libcrypto (loaded from libcrypto.num)
+# 'ssl' : belongs in libssl (loaded from libssl.num)
+# 'other' : belongs in libcrypto or libssl (loaded from other.syms)
+# 'internal' : Internal
+# 'public' : Public (generic name or external documentation)
+# Any of these values except 'public' may be prefixed with 'missing_'
+# to indicate that they are known to be missing.
+my %state;
+# %missing is affected by loading util/missing*.txt.  Values may be one of:
+# 'crypto' : belongs in libcrypto (loaded from libcrypto.num)
+# 'ssl' : belongs in libssl (loaded from libssl.num)
+# 'other' : belongs in libcrypto or libssl (loaded from other.syms)
+# 'internal' : Internal
+my %missing;
+
 # Parse libcrypto.num, etc., and return sorted list of what's there.
-sub parsenum {
+sub loadnum ($;$) {
     my $file = shift;
-    my @apis;
+    my $type = shift;
+    my @symbols;
 
     open my $IN, '<', catfile($config{sourcedir}, $file)
         or die "Can't open $file, $!, stopped";
@@ -752,44 +777,54 @@ sub parsenum {
         next if /^#/;
         next if /\bNOEXIST\b/;
         my @fields = split();
-        die "Malformed line $_"
+        die "Malformed line $. in $file: $_"
             if scalar @fields != 2 && scalar @fields != 4;
-        push @apis, $fields[0];
+        $state{$fields[0].'(3)'} = $type // 'internal';
     }
-
     close $IN;
-
-    return sort @apis;
 }
 
-# Parse all the manpages, getting return map of what they document
-# (by looking at their NAME sections).
-# Map of links in each POD file; filename => [ "foo(1)", "bar(3)", ... ]
-my %link_map = ();
-# Map of names in each POD file; "name(s)" => filename
-my %name_map = ();
-
 # Load file of symbol names that we know aren't documented.
-sub loadmissing($)
+sub loadmissing($;$)
 {
     my $missingfile = shift;
-    my @missing;
+    my $type = shift;
 
     open FH, catfile($config{sourcedir}, $missingfile)
         or die "Can't open $missingfile";
     while ( <FH> ) {
         chomp;
         next if /^#/;
-        push @missing, $_;
+        $missing{$_} = $type // 'internal';
     }
     close FH;
+}
 
-    for (@missing) {
-        err("$missingfile:", "$_ is documented in $name_map{$_}")
-            if !$opt_o && exists $name_map{$_} && defined $name_map{$_};
+# Check that we have consistent public / internal documentation and declaration
+sub checkstate () {
+    # Collect all known names, no matter where they come from
+    my %names = map { $_ => 1 } (keys %name_map, keys %state, keys %missing);
+
+    # Check section 3, i.e. functions and macros
+    foreach ( grep { $_ =~ /\(3\)$/ } sort keys %names ) {
+        next if ( $name_map{$_} // '') eq '' || $_ =~ /$ignored/;
+
+        # If a man-page isn't recorded public or if it's recorded missing
+        # and internal, it's declared to be internal.
+        my $declared_internal =
+            ($state{$_} // 'internal') eq 'internal'
+            || ($missing{$_} // '') eq 'internal';
+        # If a man-page isn't recorded internal or if it's recorded missing
+        # and not internal, it's declared to be public
+        my $declared_public =
+            ($state{$_} // 'internal') ne 'internal'
+            || ($missing{$_} // 'internal') ne 'internal';
+
+        err("$_ is supposedly public but is documented as internal")
+            if ( $declared_public && $name_map{$_} =~ /\/internal\// );
+        err("$_ is supposedly internal but is documented as public")
+            if ( $declared_internal && $name_map{$_} !~ /\/internal\// );
     }
-
-    return @missing;
 }
 
 # Check for undocumented macros; ignore those in the "missing" file
@@ -797,13 +832,6 @@ sub loadmissing($)
 sub checkmacros {
     my $count = 0;
     my %seen;
-    my @missing;
-
-    if ( $opt_o ) {
-        @missing = loadmissing('util/missingmacro111.txt');
-    } elsif ( $opt_v ) {
-        @missing = loadmissing('util/missingmacro.txt');
-    }
 
     foreach my $f ( files(TAGS => 'public_header') ) {
         # Skip some internals we don't want to document yet.
@@ -816,16 +844,10 @@ sub checkmacros {
         while ( <IN> ) {
             next unless /^#\s*define\s*(\S+)\(/;
             my $macro = "$1(3)"; # We know they're all in section 3
-            next if exists $name_map{$macro} || defined $seen{$macro};
-            next if $macro =~ /^i2d_/
-                || $macro =~ /^d2i_/
-                || $macro =~ /^DEPRECATEDIN/
-                || $macro =~ /\Q_fnsig(3)\E$/
-                || $macro =~ /^IMPLEMENT_/
-                || $macro =~ /^_?DECLARE_/;
-
-            # Skip macros known to be missing
-            next if $opt_v && grep( /^\Q$macro\E$/, @missing);
+            next if defined $name_map{$macro}
+                || defined $missing{$macro}
+                || defined $seen{$macro}
+                || $macro =~ /$ignored/;
 
             err("$f:", "macro $macro undocumented")
                 if $opt_d || $opt_e;
@@ -840,39 +862,38 @@ sub checkmacros {
 
 # Find out what is undocumented (filtering out the known missing ones)
 # and display them.
-sub printem {
-    my $libname = shift;
-    my $numfile = shift;
-    my $missingfile = shift;
+sub printem ($) {
+    my $type = shift;
     my $count = 0;
     my %seen;
 
-    my @missing = loadmissing($missingfile) if $opt_v;
-
-    foreach my $func ( parsenum($numfile) ) {
+    foreach my $func ( grep { $_ eq $type } sort keys %state ) {
         $func .= '(3)';         # We know they're all in section 3
-        next if exists $name_map{$func} || defined $seen{$func};
 
-        # Skip functions known to be missing.
-        next if $opt_v && grep( /^\Q$func\E$/, @missing);
+        # Skip functions known to be missing
+        next if $opt_v && defined $name_map{$func} && $name_map{$func} eq '';
 
-        err("$libname:", "function $func undocumented")
+        # Skip known names
+        next if defined $name_map{$func} || defined $seen{$func};
+
+        err("$type:", "function $func undocumented")
             if $opt_d || $opt_e;
         $count++;
         $seen{$func} = 1;
     }
-    err("# $count in $numfile are not documented")
+    err("# $count lib$type names are not documented")
         if $count > 0;
 }
 
 # Collect all the names in a manpage.
 sub collectnames {
-    my $filename = shift;
+    my %podinfo = @_;
+    my $filename = $podinfo{filename};
     $filename =~ m|man(\d)/|;
     my $section = $1;
     my $simplename = basename($filename, ".pod");
     my $id = "${filename}:1:";
-    my %podinfo = extract_pod_info($filename, { debug => $debug });
+    my $is_generic = $podinfo{contents} =~ /^=for openssl generic/ms;
 
     unless ( grep { $simplename eq $_ } @{$podinfo{names}} ) {
         err($id, "$simplename not in NAME section");
@@ -883,12 +904,15 @@ sub collectnames {
         err($id, "'$name' contains white space")
             if $name =~ /\s/;
         my $name_sec = "$name($section)";
-        if ( !exists $name_map{$name_sec} ) {
+        if ( !defined $name_map{$name_sec} ) {
             $name_map{$name_sec} = $filename;
+            $state{$name_sec} =
+                ( $filename =~ /\/internal\// ? 'internal' : 'public' )
+                if $is_generic;
         } elsif ( $filename eq $name_map{$name_sec} ) {
             err($id, "$name_sec duplicated in NAME section of",
                  $name_map{$name_sec});
-        } else {
+        } elsif ( $name_map{$name_sec} ne '' ) {
             err($id, "$name_sec also in NAME section of",
                  $name_map{$name_sec});
         }
@@ -896,7 +920,8 @@ sub collectnames {
 
     if ( $podinfo{contents} =~ /=for openssl foreign manual (.*)\n/ ) {
         foreach my $f ( split / /, $1 ) {
-            $name_map{$f} = undef; # It still exists!
+            $name_map{$f} = ''; # It still exists!
+            $state{$f} = 'public'; # We assume!
         }
     }
 
@@ -918,24 +943,15 @@ sub checklinks {
     foreach my $filename ( sort keys %link_map ) {
         foreach my $link ( @{$link_map{$filename}} ) {
             err("${filename}:1:", "reference to non-existing $link")
-                unless exists $name_map{$link};
+                unless defined $name_map{$link} || defined $missing{$link};
+            err("${filename}:1:", "reference of internal $link in public documentation $filename")
+                if ( ( ($state{$link} // '') eq 'internal'
+                       || ($missing{$link} // '') eq 'internal' )
+                     && $filename !~ /\/internal\// );
         }
     }
 }
 
-# Load the public symbol/macro names
-sub publicize {
-    foreach my $name ( parsenum('util/libcrypto.num') ) {
-        $public{$name} = 1;
-    }
-    foreach my $name ( parsenum('util/libssl.num') ) {
-        $public{$name} = 1;
-    }
-    foreach my $name ( parsenum('util/other.syms') ) {
-        $public{$name} = 1;
-    }
-}
-
 # Cipher/digests to skip if they show up as "not implemented"
 # because they are, via the "-*" construct.
 my %skips = (
@@ -1062,26 +1078,42 @@ if ( $opt_c ) {
     exit $status;
 }
 
-# Preparation for some options, populate %name_map and %link_map
-if ( $opt_l || $opt_u || $opt_v ) {
-    foreach ( files(TAGS => 'manual') ) {
-        collectnames($_);
+# Populate %state
+loadnum('util/libcrypto.num', 'crypto');
+loadnum('util/libssl.num', 'ssl');
+loadnum('util/other.syms', 'other');
+loadnum('util/other-internal.syms');
+if ( $opt_o ) {
+    loadmissing('util/missingmacro111.txt', 'crypto');
+    loadmissing('util/missingcrypto111.txt', 'crypto');
+    loadmissing('util/missingssl111.txt', 'ssl');
+} else {
+    loadmissing('util/missingmacro.txt', 'crypto');
+    loadmissing('util/missingcrypto.txt', 'crypto');
+    loadmissing('util/missingssl.txt', 'ssl');
+    loadmissing('util/missingcrypto-internal.txt');
+    loadmissing('util/missingssl-internal.txt');
+}
+
+if ( $opt_n || $opt_l || $opt_u || $opt_v ) {
+    my @files_to_read = ( $opt_n && @ARGV ) ? @ARGV : files(TAGS => 'manual');
+
+    foreach (@files_to_read) {
+        my %podinfo = extract_pod_info($_, { debug => $debug });
+
+        collectnames(%podinfo)
+            if ( $opt_l || $opt_u || $opt_v );
+
+        check(%podinfo)
+            if ( $opt_n );
     }
 }
 
 if ( $opt_l ) {
-    foreach my $func ( loadmissing("util/missingcrypto.txt") ) {
-        $name_map{$func} = undef;
-    }
     checklinks();
 }
 
 if ( $opt_n ) {
-    publicize();
-    foreach ( @ARGV ? @ARGV : files(TAGS => 'manual') ) {
-        check($_);
-    }
-
     # If not given args, check that all man1 commands are named properly.
     if ( scalar @ARGV == 0 ) {
         foreach ( files(TAGS => [ 'public_manual', 'man1' ]) ) {
@@ -1091,14 +1123,11 @@ if ( $opt_n ) {
     }
 }
 
+checkstate();
+
 if ( $opt_u || $opt_v) {
-    if ( $opt_o ) {
-        printem('crypto', 'util/libcrypto.num', 'util/missingcrypto111.txt');
-        printem('ssl', 'util/libssl.num', 'util/missingssl111.txt');
-    } else {
-        printem('crypto', 'util/libcrypto.num', 'util/missingcrypto.txt');
-        printem('ssl', 'util/libssl.num', 'util/missingssl.txt');
-    }
+    printem('crypto');
+    printem('ssl');
     checkmacros();
 }