]> git.ipfire.org Git - thirdparty/openssl.git/blobdiff - util/find-doc-nits
apps & al : Fix various typos, repeated words, align some spelling to LDP.
[thirdparty/openssl.git] / util / find-doc-nits
index d2317459ec04f1f1076e06c8f2fd7b2d2849177d..04911fae51588464134dcf1d2ff984d0e326689c 100755 (executable)
@@ -1,5 +1,5 @@
 #! /usr/bin/env perl
-# Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright 2002-2021 The OpenSSL Project Authors. All Rights Reserved.
 #
 # Licensed under the Apache License 2.0 (the "License").  You may not use
 # this file except in compliance with the License.  You can obtain a copy
@@ -28,9 +28,6 @@ use configdata;
 # Set to 1 for debug output
 my $debug = 0;
 
-# Where to find openssl command
-my $openssl = "./util/opensslwrap.sh";
-
 # Options.
 our($opt_d);
 our($opt_e);
@@ -38,6 +35,7 @@ our($opt_s);
 our($opt_o);
 our($opt_h);
 our($opt_l);
+our($opt_m);
 our($opt_n);
 our($opt_p);
 our($opt_u);
@@ -48,11 +46,12 @@ our($opt_c);
 sub help {
     print <<EOF;
 Find small errors (nits) in documentation.  Options:
-    -c List undocumented commands and options
+    -c List undocumented commands, undocumented options and unimplemented options.
     -d Detailed list of undocumented (implies -u)
     -e Detailed list of new undocumented (implies -v)
     -h Print this help message
     -l Print bogus links
+    -m Name(s) of manuals to focus on. Default: man1,man3,man5,man7
     -n Print nits in POD pages
     -o Causes -e/-v to count symbols added since 1.1.1 as new (implies -v)
     -u Count undocumented functions
@@ -61,7 +60,7 @@ EOF
     exit;
 }
 
-getopts('cdehlnouv');
+getopts('cdehlm:nouv');
 
 help() if $opt_h;
 $opt_u = 1 if $opt_d;
@@ -81,7 +80,11 @@ my $temp = '/tmp/docnits.txt';
 my $OUT;
 my $status = 0;
 
-my @sections = ( 'man1', 'man3', 'man5', 'man7' );
+$opt_m = "man1,man3,man5,man7" unless $opt_m;
+die "Argument of -m option may contain only man1, man3, man5, and/or man7"
+    unless $opt_m =~ /^(man[1357][, ]?)*$/;
+my @sections = ( split /[, ]/, $opt_m );
+
 my %mandatory_sections = (
     '*' => [ 'NAME', 'DESCRIPTION', 'COPYRIGHT' ],
     1   => [ 'SYNOPSIS', 'OPTIONS' ],
@@ -91,15 +94,23 @@ my %mandatory_sections = (
                          );
 
 # Symbols that we ignored.
-# They are internal macros that we currently don't document
+# They are reserved macros that we currently don't document
 my $ignored = qr/(?| ^i2d_
                  |   ^d2i_
                  |   ^DEPRECATEDIN
+                 |   ^OSSL_DEPRECATED
                  |   \Q_fnsig(3)\E$
                  |   ^IMPLEMENT_
                  |   ^_?DECLARE_
+                 |   ^sk_
+                 |   ^SKM_DEFINE_STACK_OF_INTERNAL
+                 |   ^lh_
+                 |   ^DEFINE_LHASH_OF_(INTERNAL|DEPRECATED)
                  )/x;
 
+# A common regexp for C symbol names
+my $C_symbol = qr/\b[[:alpha:]][_[:alnum:]]*\b/;
+
 # 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
 # tags to them.  For example, internal docs will have the word "internal"
@@ -143,7 +154,7 @@ my %collected_results = ();
 #                       - exclusive selectors, only applicable together with
 #                         any of the manual selectors.  If any of these are
 #                         present, only the manuals from the given sections
-#                         will be include.  If none of these are present,
+#                         will be included.  If none of these are present,
 #                         the manuals from all sections will be returned.
 #
 # All returned manual files come from configdata.pm.
@@ -311,50 +322,96 @@ sub name_synopsis {
     # Find all functions in SYNOPSIS
     return unless $contents =~ /=head1 SYNOPSIS(.*)=head1 DESCRIPTION/ms;
     my $syn = $1;
-    foreach my $line ( split /\n+/, $syn ) {
-        next unless $line =~ /^\s/;
+    my $ignore_until = undef;   # If defined, this is a regexp
+    # Remove all non-code lines
+    $syn =~ s/^(?:\s*?|\S.*?)$//msg;
+    # Remove all comments
+    $syn =~ s/\/\*.*?\*\///msg;
+    while ( $syn ) {
+        # "env" lines end at a newline.
+        # Preprocessor lines start with a # and end at a newline.
+        # Other lines end with a semicolon, and may cover more than
+        # one physical line.
+        if ( $syn !~ /^ \s*(env .*?|#.*?|.*?;)\s*$/ms ) {
+            err($id, "Can't parse rest of synopsis:\n$syn\n(declarations not ending with a semicolon (;)?)");
+            last;
+        }
+        my $line = $1;
+        $syn = $';
+
+        print STDERR "DEBUG[name_synopsis] \$line = '$line'\n" if $debug;
+
+        # Special code to skip over documented structures
+        if ( defined $ignore_until) {
+            next if $line !~ /$ignore_until/;
+            $ignore_until = undef;
+            next;
+        }
+        if ( $line =~ /^\s*(?:typedef\s+)?struct(?:\s+\S+)\s*\{/ ) {
+            $ignore_until = qr/\}.*?;/;
+            next;
+        }
+
         my $sym;
         my $is_prototype = 1;
+        $line =~ s/LHASH_OF\([^)]+\)/int/g;
         $line =~ s/STACK_OF\([^)]+\)/int/g;
         $line =~ s/SPARSE_ARRAY_OF\([^)]+\)/int/g;
         $line =~ s/__declspec\([^)]+\)//;
-        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");
-        }
+
+        ## We don't prohibit that space, to allow typedefs looking like
+        ## this:
+        ##
+        ## typedef int (fantastically_long_name_breaks_80char_limit)
+        ##     (fantastically_long_name_breaks_80char_limit *something);
+        ##
+        #if ( $line =~ /typedef.*\(\*?\S+\)\s+\(/ ) {
+        #    # a callback function with whitespace before the argument list:
+        #    # typedef ... (*NAME) (...
+        #    # typedef ... (NAME) (...
+        #    err($id, "Function typedef has space before arg list: $line");
+        #}
+
         if ( $line =~ /env (\S*)=/ ) {
             # environment variable env NAME=...
             $sym = $1;
-        } elsif ( $line =~ /typedef.*\(\*(\S+)\)\s*\(/ ) {
+        } elsif ( $line =~ /typedef.*\(\*?($C_symbol)\)\s*\(/ ) {
             # a callback function pointer: typedef ... (*NAME)(...
+            # a callback function signature: typedef ... (NAME)(...
             $sym = $1;
-        } elsif ( $line =~ /typedef.* (\S+)\(/ ) {
+        } elsif ( $line =~ /typedef.*($C_symbol)\s*\(/ ) {
             # a callback function signature: typedef ... NAME(...
             $sym = $1;
-        } elsif ( $line =~ /typedef.* (\S+);/ ) {
+        } elsif ( $line =~ /typedef.*($C_symbol);/ ) {
             # a simple typedef: typedef ... NAME;
             $is_prototype = 0;
             $sym = $1;
-        } elsif ( $line =~ /enum (\S*) \{/ ) {
+        } elsif ( $line =~ /enum ($C_symbol) \{/ ) {
             # an enumeration: enum ... {
             $sym = $1;
-        } elsif ( $line =~ /#(?:define|undef) ([A-Za-z0-9_]+)/ ) {
+        } elsif ( $line =~ /#\s*(?:define|undef) ($C_symbol)/ ) {
             $is_prototype = 0;
             $sym = $1;
-        } elsif ( $line =~ /([A-Za-z0-9_]+)\(/ ) {
+        } elsif ( $line =~ /^[^\(]*?\(\*($C_symbol)\s*\(/ ) {
+            # a function returning a function pointer: TYPE (*NAME(args))(args)
+            $sym = $1;
+        } elsif ( $line =~ /^[^\(]*?($C_symbol)\s*\(/ ) {
+            # a simple function declaration
             $sym = $1;
         }
         else {
             next;
         }
+
+        print STDERR "DEBUG[name_synopsis] \$sym = '$sym'\n" if $debug;
+
         err($id, "$sym missing from NAME section")
             unless defined $names{$sym};
         $names{$sym} = 2;
 
         # Do some sanity checks on the prototype.
         err($id, "Prototype missing spaces around commas: $line")
-            if $is_prototype && $line =~ /[a-z0-9],[^ ]/;
+            if $is_prototype && $line =~ /[a-z0-9],[^\s]/;
     }
 
     foreach my $n ( keys %names ) {
@@ -492,8 +549,10 @@ sub option_check {
         err($id, "Malformed option [1] in SYNOPSIS: $&");
     }
 
+    my @synopsis;
     while ( $synopsis =~ /$markup_re/msg ) {
         my $found = $&;
+        push @synopsis, $found if $found =~ /^B<-/;
         print STDERR "$id:DEBUG[option_check] SYNOPSIS: found $found\n"
             if $debug;
         my $option_uw = normalise_option($id, $filename, $found);
@@ -503,6 +562,7 @@ sub option_check {
 
     # In OPTIONS, we look for =item paragraphs.
     # (?=^\s*$) detects an empty line.
+    my @options;
     while ( $options =~ /=item\s+(.*?)(?=^\s*$)/msg ) {
         my $item = $&;
 
@@ -516,8 +576,19 @@ sub option_check {
             my $option_uw = normalise_option($id, $filename, $found);
             err($id, "Malformed option in OPTIONS: $found")
                 if defined $option_uw && $option_uw eq '';
+            if ($found =~ /^B<-/) {
+                push @options, $found;
+                err($id, "OPTIONS entry $found missing from SYNOPSIS")
+                    unless (grep /^\Q$found\E$/, @synopsis)
+                         || $id =~ /(openssl|-options)\.pod:1:$/;
+            }
         }
     }
+    foreach (@synopsis) {
+        my $option = $_;
+        err($id, "SYNOPSIS entry $option missing from OPTIONS")
+            unless (grep /^\Q$option\E$/, @options);
+    }
 }
 
 # Normal symbol form
@@ -551,43 +622,52 @@ sub functionname_check {
 
 # This is from http://man7.org/linux/man-pages/man7/man-pages.7.html
 my %preferred_words = (
+    '16bit'         => '16-bit',
     'a.k.a.'        => 'aka',
     'bitmask'       => 'bit mask',
     'builtin'       => 'built-in',
    #'epoch'         => 'Epoch', # handled specially, below
+    'fall-back'     => 'fallback',
     'file name'     => 'filename',
     'file system'   => 'filesystem',
     'host name'     => 'hostname',
     'i-node'        => 'inode',
     'lower case'    => 'lowercase',
     'lower-case'    => 'lowercase',
+    'manpage'       => 'man page',
+    'non-blocking'  => 'nonblocking',
+    'non-default'   => 'nondefault',
+    'non-empty'     => 'nonempty',
+    'non-negative'  => 'nonnegative',
     'non-zero'      => 'nonzero',
     'path name'     => 'pathname',
+    'pre-allocated' => 'preallocated',
     'pseudo-terminal' => 'pseudoterminal',
-    'reserved port' => 'privileged port',
-    'system port'   => 'privileged port',
-    'realtime'      => 'real-time',
     'real time'     => 'real-time',
+    'realtime'      => 'real-time',
+    'reserved port' => 'privileged port',
     'runtime'       => 'run time',
     'saved group ID'=> 'saved set-group-ID',
     'saved set-GID' => 'saved set-group-ID',
-    'saved user ID' => 'saved set-user-ID',
     'saved set-UID' => 'saved set-user-ID',
+    'saved user ID' => 'saved set-user-ID',
     'set-GID'       => 'set-group-ID',
-    'setgid'        => 'set-group-ID',
     'set-UID'       => 'set-user-ID',
+    'setgid'        => 'set-group-ID',
     'setuid'        => 'set-user-ID',
-    'super user'    => 'superuser',
-    'super-user'    => 'superuser',
+    'sub-system'    => 'subsystem',
     'super block'   => 'superblock',
     'super-block'   => 'superblock',
+    'super user'    => 'superuser',
+    'super-user'    => 'superuser',
+    'system port'   => 'privileged port',
     'time stamp'    => 'timestamp',
     'time zone'     => 'timezone',
     'upper case'    => 'uppercase',
     'upper-case'    => 'uppercase',
     'useable'       => 'usable',
-    'userspace'     => 'user space',
     'user name'     => 'username',
+    'userspace'     => 'user space',
     'zeroes'        => 'zeros'
 );
 
@@ -650,9 +730,8 @@ sub check {
         next if $target =~ /openssl-?/;
         next if ( grep { basename($_) eq "$target.pod" }
                   files(TAGS => [ 'manual', 'man1' ]) );
-        # TODO: Filter out "foreign manual" links.
         next if $target =~ /ps|apropos|sha1sum|procmail|perl/;
-        err($id, "Bad command link L<$target(1)>");
+        err($id, "Bad command link L<$target(1)>") if grep /man1/, @sections;
     }
     # Check for proper in-man-3 API links.
     while ( $contents =~ /L<([^>]*)\(3\)(?:\/.*)?>/g ) {
@@ -717,7 +796,8 @@ sub check {
 
     open my $OUT, '>', $temp
         or die "Can't open $temp, $!";
-    podchecker($filename, $OUT);
+    err($id, "POD errors")
+        if podchecker($filename, $OUT) != 0;
     close $OUT;
     open $OUT, '<', $temp
         or die "Can't read $temp, $!";
@@ -823,7 +903,7 @@ sub checkstate () {
 
         err("$_ is supposedly public but is documented as internal")
             if ( $declared_public && $name_map{$_} =~ /\/internal\// );
-        err("$_ is supposedly internal but is documented as public")
+        err("$_ is supposedly internal (maybe missing from other.syms) but is documented as public")
             if ( $declared_internal && $name_map{$_} !~ /\/internal\// );
     }
 }
@@ -919,16 +999,27 @@ sub collectnames {
         }
     }
 
-    my @links =
-        $podinfo{contents} =~ /L<
-                              # if the link is of the form L<something|name(s)>,
-                              # then remove 'something'.  Note that 'something'
-                              # may contain POD codes as well...
-                              (?:(?:[^\|]|<[^>]*>)*\|)?
-                              # we're only interested in references that have
-                              # a one digit section number
-                              ([^\/>\(]+\(\d\))
-                             /gx;
+    my @links = ();
+    # Don't use this regexp directly on $podinfo{contents}, as it causes
+    # a regexp recursion, which fails on really big PODs.  Instead, use
+    # $markup_re to pick up general markup, and use this regexp to check
+    # that the markup that was found is indeed a link.
+    my $linkre = qr/L<
+                    # if the link is of the form L<something|name(s)>,
+                    # then remove 'something'.  Note that 'something'
+                    # may contain POD codes as well...
+                    (?:(?:[^\|]|<[^>]*>)*\|)?
+                    # we're only interested in references that have
+                    # a one digit section number
+                    ([^\/>\(]+\(\d\))
+                   /x;
+    while ( $podinfo{contents} =~ /$markup_re/msg ) {
+        my $x = $1;
+
+        if ($x =~ $linkre) {
+            push @links, $1;
+        }
+    }
     $link_map{$filename} = [ @links ];
 }
 
@@ -965,21 +1056,40 @@ my %skips = (
     'digest' => 1,
 );
 
+my %genopts; # generic options parsed from apps/include/opt.h
+
 # Check the flags of a command and see if everything is in the manpage
 sub checkflags {
     my $cmd = shift;
     my $doc = shift;
-    my %cmdopts;
+    my @cmdopts;
     my %docopts;
-    my %localskips;
 
-    # Get the list of options in the command.
-    open CFH, "$openssl list --options $cmd|"
-        or die "Can list options for $cmd, $!";
+    # Get the list of options in the command source file.
+    my $active = 0;
+    my $expect_helpstr = "";
+    open CFH, "apps/$cmd.c"
+        or die "Can't open apps/$cmd.c to list options for $cmd, $!";
     while ( <CFH> ) {
         chop;
-        s/ .$//;
-        $cmdopts{$_} = 1;
+        if ($active) {
+            last if m/^\s*};/;
+            if ($expect_helpstr ne "") {
+                next if m/^\s*#\s*if/;
+                err("$cmd does not implement help for -$expect_helpstr") unless m/^\s*"/;
+                $expect_helpstr = "";
+            }
+            if (m/\{\s*"([^"]+)"\s*,\s*OPT_[A-Z0-9_]+\s*,\s*('[-\/:<>cEfFlMnNpsuU]'|0)(.*)$/
+                    && !($cmd eq "s_client" && $1 eq "wdebug")) {
+                push @cmdopts, $1;
+                $expect_helpstr = $1;
+                $expect_helpstr = "" if $3 =~ m/^\s*,\s*"/;
+            } elsif (m/[\s,](OPT_[A-Z]+_OPTIONS?)\s*(,|$)/) {
+                push @cmdopts, @{ $genopts{$1} };
+            }
+        } elsif (m/^const\s+OPTIONS\s*/) {
+            $active = 1;
+        }
     }
     close CFH;
 
@@ -989,12 +1099,6 @@ sub checkflags {
     while ( <CFH> ) {
         chop;
         last if /DESCRIPTION/;
-        if ( /=for openssl ifdef (.*)/ ) {
-            foreach my $f ( split / /, $1 ) {
-                $localskips{$f} = 1;
-            }
-            next;
-        }
         my $opt;
         if ( /\[B<-([^ >]+)/ ) {
             $opt = $1;
@@ -1009,16 +1113,16 @@ sub checkflags {
     close CFH;
 
     # See what's in the command not the manpage.
-    my @undocced = sort grep { !defined $docopts{$_} } keys %cmdopts;
+    my @undocced = sort grep { !defined $docopts{$_} } @cmdopts;
     foreach ( @undocced ) {
-        next if /-/; # Skip the -- end-of-flags marker
-        err("$doc: undocumented option -$_");
+        err("$doc: undocumented $cmd option -$_");
     }
 
     # See what's in the command not the manpage.
-    my @unimpl = sort grep { !defined $cmdopts{$_} } keys %docopts;
+    my @unimpl = sort grep { my $e = $_; !(grep /^\Q$e\E$/, @cmdopts) } keys %docopts;
     foreach ( @unimpl ) {
-        next if defined $skips{$_} || defined $localskips{$_};
+        next if $_ eq "-"; # Skip the -- end-of-flags marker
+        next if defined $skips{$_};
         err("$doc: $cmd does not implement -$_");
     }
 }
@@ -1033,18 +1137,27 @@ sub checkflags {
 if ( $opt_c ) {
     my @commands = ();
 
-    # Get list of commands.
-    open FH, "$openssl list -1 -commands|"
-        or die "Can't list commands, $!";
-    while ( <FH> ) {
+    # Get the lists of generic options.
+    my $active = "";
+    open OFH, catdir($config{sourcedir}, "apps/include/opt.h")
+        or die "Can't open apps/include/opt.h to list generic options, $!";
+    while ( <OFH> ) {
         chop;
-        push @commands, $_;
+        push @{ $genopts{$active} }, $1 if $active ne "" && m/^\s+\{\s*"([^"]+)"\s*,\s*OPT_/;
+        $active = $1 if m/^\s*#\s*define\s+(OPT_[A-Z]+_OPTIONS?)\s*\\\s*$/;
+        $active = "" if m/^\s*$/;
     }
-    close FH;
+    close OFH;
+
+    # Get list of commands.
+    opendir(DIR, "apps");
+    @commands = grep(/\.c$/, readdir(DIR));
+    closedir(DIR);
 
     # See if each has a manpage.
     foreach my $cmd ( @commands ) {
-        next if $cmd eq 'help' || $cmd eq 'exit';
+        $cmd =~ s/\.c$//;
+        next if $cmd eq 'progs' || $cmd eq 'vms_decc_init';
         my @doc = ( grep { basename($_) eq "openssl-$cmd.pod"
                            # For "tsget" and "CA.pl" pod pages
                            || basename($_) eq "$cmd.pod" }
@@ -1058,18 +1171,6 @@ if ( $opt_c ) {
             checkflags($cmd, @doc);
         }
     }
-
-    # See what help is missing.
-    open FH, "$openssl list --missing-help |"
-        or die "Can't list missing help, $!";
-    while ( <FH> ) {
-        chop;
-        my ($cmd, $flag) = split;
-        err("$cmd has no help for -$flag");
-    }
-    close FH;
-
-    exit $status;
 }
 
 # Populate %state
@@ -1109,9 +1210,10 @@ if ( $opt_l ) {
 
 if ( $opt_n ) {
     # If not given args, check that all man1 commands are named properly.
-    if ( scalar @ARGV == 0 ) {
+    if ( scalar @ARGV == 0 && grep /man1/, @sections ) {
         foreach ( files(TAGS => [ 'public_manual', 'man1' ]) ) {
-            next if /CA.pl/ || /openssl\.pod/ || /tsget\.pod/;
+            next if /openssl\.pod/
+                || /CA\.pl/ || /tsget\.pod/; # these commands are special cases
             err("$_ doesn't start with openssl-") unless /openssl-/;
         }
     }