Revise EVP_PKEY param handling
[openssl.git] / util / find-doc-nits
1 #! /usr/bin/env perl
2 # Copyright 2002-2019 The OpenSSL Project Authors. All Rights Reserved.
3 #
4 # Licensed under the Apache License 2.0 (the "License").  You may not use
5 # this file except in compliance with the License.  You can obtain a copy
6 # in the file LICENSE in the source distribution or at
7 # https://www.openssl.org/source/license.html
8
9
10 require 5.10.0;
11 use warnings;
12 use strict;
13 use Pod::Checker;
14 use File::Find;
15 use File::Basename;
16 use File::Spec::Functions;
17 use Getopt::Std;
18 use lib catdir(dirname($0), "perl");
19 use OpenSSL::Util::Pod;
20
21 # Options.
22 our($opt_d);
23 our($opt_e);
24 our($opt_s);
25 our($opt_o);
26 our($opt_h);
27 our($opt_l);
28 our($opt_n);
29 our($opt_p);
30 our($opt_u);
31 our($opt_v);
32 our($opt_c);
33
34 sub help()
35 {
36     print <<EOF;
37 Find small errors (nits) in documentation.  Options:
38     -d Detailed list of undocumented (implies -u)
39     -e Detailed list of new undocumented (implies -v)
40     -s Same as -e except no output is generated if nothing is undocumented
41     -o Causes -e/-v to count symbols added since 1.1.1 as new (implies -v)
42     -l Print bogus links
43     -n Print nits in POD pages
44     -p Warn if non-public name documented (implies -n)
45     -u Count undocumented functions
46     -v Count new undocumented functions
47     -h Print this help message
48     -c List undocumented commands and options
49 EOF
50     exit;
51 }
52
53 my $temp = '/tmp/docnits.txt';
54 my $OUT;
55 my %public;
56
57 my %mandatory_sections =
58     ( '*'    => [ 'NAME', 'DESCRIPTION', 'COPYRIGHT' ],
59       1      => [ 'SYNOPSIS', 'OPTIONS' ],
60       3      => [ 'SYNOPSIS', 'RETURN VALUES' ],
61       5      => [ ],
62       7      => [ ] );
63
64 # Cross-check functions in the NAME and SYNOPSIS section.
65 sub name_synopsis()
66 {
67     my $id = shift;
68     my $filename = shift;
69     my $contents = shift;
70
71     # Get NAME section and all words in it.
72     return unless $contents =~ /=head1 NAME(.*)=head1 SYNOPSIS/ms;
73     my $tmp = $1;
74     $tmp =~ tr/\n/ /;
75     print "$id trailing comma before - in NAME\n" if $tmp =~ /, *-/;
76     $tmp =~ s/ -.*//g;
77     print "$id POD markup among the names in NAME\n" if $tmp =~ /[<>]/;
78     $tmp =~ s/  */ /g;
79     print "$id missing comma in NAME\n" if $tmp =~ /[^,] /;
80
81     my $dirname = dirname($filename);
82     my $simplename = basename(basename($filename, ".in"), ".pod");
83     my $foundfilename = 0;
84     my %foundfilenames = ();
85     my %names;
86     foreach my $n ( split ',', $tmp ) {
87         $n =~ s/^\s+//;
88         $n =~ s/\s+$//;
89         print "$id the name '$n' contains white-space\n"
90             if $n =~ /\s/;
91         $names{$n} = 1;
92         $foundfilename++ if $n eq $simplename;
93         $foundfilenames{$n} = 1
94             if ((-f "$dirname/$n.pod.in" || -f "$dirname/$n.pod")
95                 && $n ne $simplename);
96     }
97     print "$id the following exist as other .pod or .pod.in files:\n",
98         join(" ", sort keys %foundfilenames), "\n"
99         if %foundfilenames;
100     print "$id $simplename (filename) missing from NAME section\n"
101         unless $foundfilename;
102     foreach my $n ( keys %names ) {
103         print "$id $n is not public\n"
104             if $opt_p and !defined $public{$n};
105     }
106
107     # Find all functions in SYNOPSIS
108     return unless $contents =~ /=head1 SYNOPSIS(.*)=head1 DESCRIPTION/ms;
109     my $syn = $1;
110     foreach my $line ( split /\n+/, $syn ) {
111         next unless $line =~ /^\s/;
112         my $sym;
113         $line =~ s/STACK_OF\([^)]+\)/int/g;
114         $line =~ s/SPARSE_ARRAY_OF\([^)]+\)/int/g;
115         $line =~ s/__declspec\([^)]+\)//;
116         if ( $line =~ /env (\S*)=/ ) {
117             # environment variable env NAME=...
118             $sym = $1;
119         } elsif ( $line =~ /typedef.*\(\*(\S+)\)\(.*/ ) {
120             # a callback function pointer: typedef ... (*NAME)(...
121             $sym = $1;
122         } elsif ( $line =~ /typedef.* (\S+)\(.*/ ) {
123             # a callback function signature: typedef ... NAME(...
124             $sym = $1;
125         } elsif ( $line =~ /typedef.* (\S+);/ ) {
126             # a simple typedef: typedef ... NAME;
127             $sym = $1;
128         } elsif ( $line =~ /enum (\S*) \{/ ) {
129             # an enumeration: enum ... {
130             $sym = $1;
131         } elsif ( $line =~ /#(?:define|undef) ([A-Za-z0-9_]+)/ ) {
132             $sym = $1;
133         } elsif ( $line =~ /([A-Za-z0-9_]+)\(/ ) {
134             $sym = $1;
135         }
136         else {
137             next;
138         }
139         print "$id $sym missing from NAME section\n"
140             unless defined $names{$sym};
141         $names{$sym} = 2;
142
143         # Do some sanity checks on the prototype.
144         print "$id prototype missing spaces around commas: $line\n"
145             if ( $line =~ /[a-z0-9],[^ ]/ );
146     }
147
148     foreach my $n ( keys %names ) {
149         next if $names{$n} == 2;
150         print "$id $n missing from SYNOPSIS\n";
151     }
152 }
153
154 # Check if SECTION ($3) is located before BEFORE ($4)
155 sub check_section_location()
156 {
157     my $id = shift;
158     my $contents = shift;
159     my $section = shift;
160     my $before = shift;
161
162     return unless $contents =~ /=head1 $section/
163         and $contents =~ /=head1 $before/;
164     print "$id $section should appear before $before section\n"
165         if $contents =~ /=head1 $before.*=head1 $section/ms;
166 }
167
168 # Check if a =head1 is duplicated, or a =headX is duplicated within a
169 # =head1.  Treats =head2 =head3 as equivalent -- it doesn't reset the head3
170 # sets if it finds a =head2 -- but that is good enough for now. Also check
171 # for proper capitalization, trailing periods, etc.
172 sub check_head_style()
173 {
174     my $id = shift;
175     my $contents = shift;
176     my %head1;
177     my %subheads;
178
179     foreach my $line ( split /\n+/, $contents ) {
180         next unless $line =~ /^=head/;
181         if ( $line =~ /head1/ ) {
182             print "$id duplicate section $line\n"
183                 if defined $head1{$line};
184             $head1{$line} = 1;
185             %subheads = ();
186         } else {
187             print "$id duplicate subsection $line\n"
188                 if defined $subheads{$line};
189             $subheads{$line} = 1;
190         }
191         print "$id period in =head\n"
192             if $line =~ /\.[^\w]/ or $line =~ /\.$/;
193         print "$id not all uppercase in =head1\n"
194             if $line =~ /head1.*[a-z]/;
195         print "$id all uppercase in subhead\n"
196             if $line =~ /head[234][ A-Z0-9]+$/;
197     }
198 }
199
200 sub check()
201 {
202     my $filename = shift;
203     my $dirname = basename(dirname($filename));
204
205     my $contents = '';
206     {
207         local $/ = undef;
208         open POD, $filename or die "Couldn't open $filename, $!";
209         $contents = <POD>;
210         close POD;
211     }
212
213     my $id = "${filename}:1:";
214     &check_head_style($id, $contents);
215
216     # Check ordering of some sections in man3
217     if ( $filename =~ m|man3/| ) {
218         &check_section_location($id, $contents, "RETURN VALUES", "EXAMPLES");
219         &check_section_location($id, $contents, "SEE ALSO", "HISTORY");
220         &check_section_location($id, $contents, "EXAMPLES", "SEE ALSO");
221     }
222
223     &name_synopsis($id, $filename, $contents)
224         unless $contents =~ /=for comment generic/
225             or $filename =~ m@man[157]/@;
226
227     print "$id doesn't start with =pod\n"
228         if $contents !~ /^=pod/;
229     print "$id doesn't end with =cut\n"
230         if $contents !~ /=cut\n$/;
231     print "$id more than one cut line.\n"
232         if $contents =~ /=cut.*=cut/ms;
233     print "$id EXAMPLE not EXAMPLES section.\n"
234         if $contents =~ /=head1 EXAMPLE[^S]/;
235     print "$id WARNING not WARNINGS section.\n"
236         if $contents =~ /=head1 WARNING[^S]/;
237     print "$id missing copyright\n"
238         if $contents !~ /Copyright .* The OpenSSL Project Authors/;
239     print "$id copyright not last\n"
240         if $contents =~ /head1 COPYRIGHT.*=head/ms;
241     print "$id head2 in All uppercase\n"
242         if $contents =~ /head2\s+[A-Z ]+\n/;
243     print "$id extra space after head\n"
244         if $contents =~ /=head\d\s\s+/;
245     print "$id period in NAME section\n"
246         if $contents =~ /=head1 NAME.*\.\n.*=head1 SYNOPSIS/ms;
247     print "$id Duplicate $1 in L<>\n"
248         if $contents =~ /L<([^>]*)\|([^>]*)>/ && $1 eq $2;
249     print "$id Bad =over $1\n"
250         if $contents =~ /=over([^ ][^24])/;
251     print "$id Possible version style issue\n"
252         if $contents =~ /OpenSSL version [019]/;
253
254     if ( $contents !~ /=for comment multiple includes/ ) {
255         # Look for multiple consecutive openssl #include lines
256         # (non-consecutive lines are okay; see man3/MD5.pod).
257         if ( $contents =~ /=head1 SYNOPSIS(.*)=head1 DESCRIPTION/ms ) {
258             my $count = 0;
259             foreach my $line ( split /\n+/, $1 ) {
260                 if ( $line =~ m@include <openssl/@ ) {
261                     print "$id has multiple includes\n" if ++$count == 2;
262                 } else {
263                     $count = 0;
264                 }
265             }
266         }
267     }
268
269     open my $OUT, '>', $temp
270         or die "Can't open $temp, $!";
271     podchecker($filename, $OUT);
272     close $OUT;
273     open $OUT, '<', $temp
274         or die "Can't read $temp, $!";
275     while ( <$OUT> ) {
276         next if /\(section\) in.*deprecated/;
277         print;
278     }
279     close $OUT;
280     unlink $temp || warn "Can't remove $temp, $!";
281
282     # Find what section this page is in; assume 3.
283     my $section = 3;
284     $section = $1 if $dirname =~ /man([1-9])/;
285
286     foreach ((@{$mandatory_sections{'*'}}, @{$mandatory_sections{$section}})) {
287         # Skip "return values" if not -s
288         print "$id: missing $_ head1 section\n"
289             if $contents !~ /^=head1\s+${_}\s*$/m;
290     }
291 }
292
293 my %dups;
294
295 sub parsenum()
296 {
297     my $file = shift;
298     my @apis;
299
300     open my $IN, '<', $file
301         or die "Can't open $file, $!, stopped";
302
303     while ( <$IN> ) {
304         next if /^#/;
305         next if /\bNOEXIST\b/;
306         my @fields = split();
307         die "Malformed line $_"
308             if scalar @fields != 2 && scalar @fields != 4;
309         push @apis, $fields[0];
310     }
311
312     close $IN;
313
314     print "# Found ", scalar(@apis), " in $file\n" unless $opt_p;
315     return sort @apis;
316 }
317
318 sub getdocced
319 {
320     my $dir = shift;
321     my %return;
322
323     foreach my $pod ( glob("$dir/*.pod"), glob("$dir/*.pod.in") ) {
324         my %podinfo = extract_pod_info($pod);
325         foreach my $n ( @{$podinfo{names}} ) {
326             $return{$n} = $pod;
327             print "# Duplicate $n in $pod and $dups{$n}\n"
328                 if defined $dups{$n} && $dups{$n} ne $pod;
329             $dups{$n} = $pod;
330         }
331     }
332
333     return %return;
334 }
335
336 my %docced;
337
338 sub loadmissing($)
339 {
340     my $missingfile = shift;
341     my @missing;
342
343     open FH, $missingfile
344         || die "Can't open $missingfile";
345     while ( <FH> ) {
346         chomp;
347         next if /^#/;
348         push @missing, $_;
349     }
350     close FH;
351
352     return @missing;
353 }
354
355 sub checkmacros()
356 {
357     my $count = 0;
358     my %seen;
359     my @missing;
360
361     if ($opt_o) {
362         @missing = loadmissing('util/missingmacro111.txt');
363     } elsif ($opt_v) {
364         @missing = loadmissing('util/missingmacro.txt');
365     }
366
367     print "# Checking macros (approximate)\n" if !$opt_s;
368     foreach my $f ( glob('include/openssl/*.h') ) {
369         # Skip some internals we don't want to document yet.
370         next if $f eq 'include/openssl/asn1.h';
371         next if $f eq 'include/openssl/asn1t.h';
372         next if $f eq 'include/openssl/err.h';
373         open(IN, $f) || die "Can't open $f, $!";
374         while ( <IN> ) {
375             next unless /^#\s*define\s*(\S+)\(/;
376             my $macro = $1;
377             next if $docced{$macro} || defined $seen{$macro};
378             next if $macro =~ /i2d_/
379                 || $macro =~ /d2i_/
380                 || $macro =~ /DEPRECATEDIN/
381                 || $macro =~ /IMPLEMENT_/
382                 || $macro =~ /DECLARE_/;
383
384             # Skip macros known to be missing
385             next if $opt_v && grep( /^$macro$/, @missing);
386     
387             print "$f:$macro\n" if $opt_d || $opt_e;
388             $count++;
389             $seen{$macro} = 1;
390         }
391         close(IN);
392     }
393     print "# Found $count macros missing\n" if !$opt_s || $count > 0;
394 }
395
396 sub printem()
397 {
398     my $libname = shift;
399     my $numfile = shift;
400     my $missingfile = shift;
401     my $count = 0;
402     my %seen;
403
404     my @missing = loadmissing($missingfile) if ($opt_v);
405
406     foreach my $func ( &parsenum($numfile) ) {
407         next if $docced{$func} || defined $seen{$func};
408
409         # Skip ASN1 utilities
410         next if $func =~ /^ASN1_/;
411
412         # Skip functions known to be missing
413         next if $opt_v && grep( /^$func$/, @missing);
414
415         print "$libname:$func\n" if $opt_d || $opt_e;
416         $count++;
417         $seen{$func} = 1;
418     }
419     print "# Found $count missing from $numfile\n\n" if !$opt_s || $count > 0;
420 }
421
422
423 # Collection of links in each POD file.
424 # filename => [ "foo(1)", "bar(3)", ... ]
425 my %link_collection = ();
426 # Collection of names in each POD file.
427 # "name(s)" => filename
428 my %name_collection = ();
429
430 sub collectnames {
431     my $filename = shift;
432     $filename =~ m|man(\d)/|;
433     my $section = $1;
434     my $simplename = basename(basename($filename, ".in"), ".pod");
435     my $id = "${filename}:1:";
436
437     my $contents = '';
438     {
439         local $/ = undef;
440         open POD, $filename or die "Couldn't open $filename, $!";
441         $contents = <POD>;
442         close POD;
443     }
444
445     $contents =~ /=head1 NAME([^=]*)=head1 /ms;
446     my $tmp = $1;
447     unless (defined $tmp) {
448         print "$id weird name section\n";
449         return;
450     }
451     $tmp =~ tr/\n/ /;
452     $tmp =~ s/ -.*//g;
453
454     my @names =
455         map { s|/|-|g; $_ }              # Treat slash as dash
456         map { s/^\s+//g; s/\s+$//g; $_ } # Trim prefix and suffix blanks
457         split(/,/, $tmp);
458     unless (grep { $simplename eq $_ } @names) {
459         print "$id missing $simplename\n";
460         push @names, $simplename;
461     }
462     foreach my $name (@names) {
463         next if $name eq "";
464         if ($name =~ /\s/) {
465             print "$id '$name' contains white space\n";
466         }
467         my $name_sec = "$name($section)";
468         if (! exists $name_collection{$name_sec}) {
469             $name_collection{$name_sec} = $filename;
470         } elsif ($filename eq $name_collection{$name_sec}) {
471             print "$id $name_sec repeated in NAME section of $name_collection{$name_sec}\n"
472         } else {
473             print "$id $name_sec also in NAME section of $name_collection{$name_sec}\n";
474         }
475     }
476
477     my @foreign_names =
478         map { map { s/\s+//g; $_ } split(/,/, $_) }
479         $contents =~ /=for\s+comment\s+foreign\s+manuals:\s*(.*)\n\n/;
480     foreach (@foreign_names) {
481         $name_collection{$_} = undef; # It still exists!
482     }
483
484     my @links = $contents =~ /L<
485                               # if the link is of the form L<something|name(s)>,
486                               # then remove 'something'.  Note that 'something'
487                               # may contain POD codes as well...
488                               (?:(?:[^\|]|<[^>]*>)*\|)?
489                               # we're only interested in references that have
490                               # a one digit section number
491                               ([^\/>\(]+\(\d\))
492                              /gx;
493     $link_collection{$filename} = [ @links ];
494 }
495
496 sub checklinks {
497     foreach my $filename (sort keys %link_collection) {
498         foreach my $link (@{$link_collection{$filename}}) {
499             print "${filename}:1: reference to non-existing $link\n"
500                 unless exists $name_collection{$link};
501         }
502     }
503 }
504
505 sub publicize() {
506     foreach my $name ( &parsenum('util/libcrypto.num') ) {
507         $public{$name} = 1;
508     }
509     foreach my $name ( &parsenum('util/libssl.num') ) {
510         $public{$name} = 1;
511     }
512     foreach my $name ( &parsenum('util/private.num') ) {
513         $public{$name} = 1;
514     }
515 }
516
517 my %skips = (
518     'aes128' => 1,
519     'aes192' => 1,
520     'aes256' => 1,
521     'aria128' => 1,
522     'aria192' => 1,
523     'aria256' => 1,
524     'camellia128' => 1,
525     'camellia192' => 1,
526     'camellia256' => 1,
527     'des' => 1,
528     'des3' => 1,
529     'idea' => 1,
530     '[cipher]' => 1,
531     '[digest]' => 1,
532 );
533
534 sub checkflags() {
535     my $cmd = shift;
536     my $doc = shift;
537     my %cmdopts;
538     my %docopts;
539     my $ok = 1;
540
541     # Get the list of options in the command.
542     open CFH, "./apps/openssl list --options $cmd|"
543         || die "Can list options for $cmd, $!";
544     while ( <CFH> ) {
545         chop;
546         s/ .$//;
547         $cmdopts{$_} = 1;
548     }
549     close CFH;
550
551     # Get the list of flags from the synopsis
552     open CFH, "<$doc"
553         || die "Can't open $doc, $!";
554     while ( <CFH> ) {
555         chop;
556         last if /DESCRIPTION/;
557         next unless /\[B<-([^ >]+)/;
558         $docopts{$1} = 1;
559     }
560     close CFH;
561
562     # See what's in the command not the manpage.
563     my @undocced = ();
564     foreach my $k ( keys %cmdopts ) {
565         push @undocced, $k unless $docopts{$k};
566     }
567     if ( scalar @undocced > 0 ) {
568         $ok = 0;
569         foreach ( @undocced ) {
570             print "doc/man1/$cmd.pod: Missing -$_\n";
571         }
572     }
573
574     # See what's in the command not the manpage.
575     my @unimpl = ();
576     foreach my $k ( keys %docopts ) {
577         push @unimpl, $k unless $cmdopts{$k};
578     }
579     if ( scalar @unimpl > 0 ) {
580         $ok = 0;
581         foreach ( @unimpl ) {
582             next if defined $skips{$_};
583             print "doc/man1/$cmd.pod: Not implemented -$_\n";
584         }
585     }
586
587     return $ok;
588 }
589
590 getopts('cdesolnphuv');
591
592 &help() if $opt_h;
593
594 $opt_n = 1 if $opt_p;
595 $opt_u = 1 if $opt_d;
596 $opt_e = 1 if $opt_s;
597 $opt_v = 1 if $opt_o || $opt_e;
598
599 die "Cannot use both -u and -v" if $opt_u && $opt_v;
600 die "Cannot use both -d and -e" if $opt_d && $opt_e;
601
602 # We only need to check c, l, n, u and v.
603 # Options d, e, s, o and p imply one of the above.
604 die "Need one of -[cdesolnpuv] flags.\n"
605     unless $opt_c or $opt_l or $opt_n or $opt_u or $opt_v;
606
607 if ( $opt_c ) {
608     my $ok = 1;
609     my @commands = ();
610
611     # Get list of commands.
612     open FH, "./apps/openssl list -1 -commands|"
613         || die "Can't list commands, $!";
614     while ( <FH> ) {
615         chop;
616         push @commands, $_;
617     }
618     close FH;
619
620     # See if each has a manpage.
621     foreach my $cmd ( @commands ) {
622         next if $cmd eq 'help' || $cmd eq 'exit';
623         my $doc = "doc/man1/$cmd.pod";
624         $doc = "doc/man1/openssl-$cmd.pod" if -f "doc/man1/openssl-$cmd.pod";
625         if ( ! -f "$doc" ) {
626             print "$doc does not exist\n";
627             $ok = 0;
628         } else {
629             $ok = 0 if not &checkflags($cmd, $doc);
630         }
631     }
632
633     # See what help is missing.
634     open FH, "./apps/openssl list --missing-help |"
635         || die "Can't list missing help, $!";
636     while ( <FH> ) {
637         chop;
638         my ($cmd, $flag) = split;
639         print "$cmd has no help for -$flag\n";
640         $ok = 0;
641     }
642     close FH;
643
644     exit 1 if not $ok;
645 }
646
647 if ( $opt_l ) {
648     foreach (@ARGV ? @ARGV : (glob('doc/*/*.pod'), glob('doc/*/*.pod.in'),
649                               glob('doc/internal/*/*.pod'))) {
650         collectnames($_);
651     }
652     checklinks();
653 }
654
655 if ( $opt_n ) {
656     &publicize() if $opt_p;
657     foreach (@ARGV ? @ARGV : (glob('doc/*/*.pod'), glob('doc/*/*.pod.in'))) {
658         &check($_);
659     }
660     {
661         local $opt_p = undef;
662         foreach (@ARGV ? @ARGV : glob('doc/internal/*/*.pod')) {
663             &check($_);
664         }
665     }
666 }
667
668 if ( $opt_u || $opt_v) {
669     my %temp = getdocced('doc/man3');
670     foreach ( keys %temp ) {
671         $docced{$_} = $temp{$_};
672     }
673     if ($opt_o) {
674         &printem('crypto', 'util/libcrypto.num', 'util/missingcrypto111.txt');
675         &printem('ssl', 'util/libssl.num', 'util/missingssl111.txt');
676     } else {
677         &printem('crypto', 'util/libcrypto.num', 'util/missingcrypto.txt');
678         &printem('ssl', 'util/libssl.num', 'util/missingssl.txt');
679     }
680     &checkmacros();
681 }
682
683 exit;