my $temp = '/tmp/docnits.txt';
my $OUT;
-my %public;
my $status = 0;
my @sections = ( 'man1', 'man3', 'man5', 'man7' );
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
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;
$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()
# 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);
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);
}
}
+# 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";
my @fields = split();
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
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.
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;
# 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");
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});
}
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!
}
}
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 = (
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' ]) ) {
}
}
+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();
}