#!/usr/bin/env perl #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at https://curl.se/docs/copyright.html. # # You may opt to use, copy, modify, merge, publish, distribute and/or sell # copies of the Software, and permit persons to whom the Software is # furnished to do so, under the terms of the COPYING file. # # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # # SPDX-License-Identifier: curl # # ########################################################################### # # Check that the deprecated statuses of functions and enum values in header # files, man pages and symbols-in-versions are in sync. use strict; use warnings; use File::Basename; my $root=$ARGV[0] || "."; my $incdir = "$root/include/curl"; my $docdir = "$root/docs"; my $libdocdir = "$docdir/libcurl"; my $errcode = 0; # Symbol-indexed hashes. # Values are: # X Not deprecated # ? Deprecated in unknown version # x.yy.z Deprecated in version x.yy.z my %syminver; # Symbols-in-versions deprecations. my %hdr; # Public header files deprecations. my %funcman; # Function man pages deprecations. my %optman; # Option man pages deprecations. # Scan header file for public function and enum values. Flag them with # the version they are deprecated in, if some. sub scan_header { my ($f)=@_; my $line = ""; my $incomment = 0; my $inenum = 0; open(my $h, "<", "$f"); while(<$h>) { s/^\s*(.*?)\s*$/$1/; # Trim. # Remove multi-line comment trail. if($incomment) { if($_ !~ /.*?\*\/\s*(.*)$/) { next; } $_ = $1; $incomment = 0; } if($line ne "") { # Unfold line. $_ = "$line $1"; $line = ""; } # Remove comments. while($_ =~ /^(.*?)\/\*.*?\*\/(.*)$/) { $_ = "$1 $2"; } if($_ =~ /^(.*)\/\*/) { $_ = "$1 "; $incomment = 1; } s/^\s*(.*?)\s*$/$1/; # Trim again. # Ignore preprocessor directives and blank lines. if($_ =~ /^(?:#|$)/) { next; } # Handle lines that may be continued as if they were folded. if($_ !~ /[;,{}]$/) { # Folded line. $line = $_; next; } if($_ =~ /CURLOPTDEPRECATED\(/) { # Handle deprecated CURLOPT_* option. if($_ !~ /CURLOPTDEPRECATED\(\s*(\S+)\s*,(?:.*?,){2}\s*(.*?)\s*,.*"\)/) { # Folded line. $line = $_; next; } $hdr{$1} = $2; } elsif($_ =~ /CURLOPT\(/) { # Handle non-deprecated CURLOPT_* option. if($_ !~ /CURLOPT\(\s*(\S+)\s*(?:,.*?){2}\)/) { # Folded line. $line = $_; next; } $hdr{$1} = "X"; } else { my $version = "X"; # Get other kind of deprecation from this line. if($_ =~ /CURL_DEPRECATED\(/) { if($_ !~ /^(.*)CURL_DEPRECATED\(\s*(\S+?)\s*,.*?"\)(.*)$/) { # Folded line. $line = $_; next; } $version = $2; $_ = "$1 $3"; } if($_ =~ /^CURL_EXTERN\s+.*\s+(\S+?)\s*\(/) { # Flag public function. $hdr{$1} = $version; } elsif($inenum && $_ =~ /(\w+)\s*[,=}]/) { # Flag enum value. $hdr{$1} = $version; } } # Remember if we are in an enum definition. $inenum |= ($_ =~ /\benum\b/); if($_ =~ /}/) { $inenum = 0; } } close $h; } # Scan function man page for options. # Each option has to be declared as ".IP <option>" where <option> starts with # the prefix. Flag each option with its deprecation version, if some. sub scan_man_for_opts { my ($f, $prefix)=@_; my $opt = ""; my $line = ""; open(my $m, "<", "$f"); while(<$m>) { if($_ =~ /^\./) { # roff directive found: end current option paragraph. my $o = $opt; $opt = ""; if($_ =~ /^\.IP\s+((?:$prefix)_\w+)/) { # A new option has been found. $opt = $1; } $_ = $line; # Get full paragraph. $line = ""; s/\\f.//g; # Remove font formatting. s/\s+/ /g; # One line with single space only. if($o) { $funcman{$o} = "X"; # Check if paragraph is mentioning deprecation. while($_ =~ /(?:deprecated|obsoleted?)\b\s*(?:in\b|since\b)?\s*(?:version\b|curl\b|libcurl\b)?\s*(\d[0-9.]*\d)?\b\s*(.*)$/i) { $funcman{$o} = $1 || "?"; $_ = $2; } } } else { # Text line: accumulate. $line .= $_; } } close $m; } # Scan man page for deprecation in DESCRIPTION and/or AVAILABILITY sections. sub scan_man_page { my ($path, $sym, $table)=@_; my $version = "X"; if(open(my $fh, "<", "$path")) { my $section = ""; my $line = ""; while(<$fh>) { if($_ =~ /\.so\s+man3\/(.*\.3\b)/) { # Handle man page inclusion. scan_man_page(dirname($path) . "/$1", $sym, $table); $version = exists($$table{$sym})? $$table{$sym}: $version; } elsif($_ =~ /^\./) { # Line is a roff directive. if($_ =~ /^\.SH\b\s*(\w*)/) { # Section starts. End previous one. my $sh = $section; $section = $1; $_ = $line; # Previous section text. $line = ""; s/\\f.//g; s/\s+/ /g; s/\\f.//g; # Remove font formatting. s/\s+/ /g; # One line with single space only. if($sh =~ /DESCRIPTION|AVAILABILITY/) { while($_ =~ /(?:deprecated|obsoleted?)\b\s*(?:in\b|since\b)?\s*(?:version\b|curl\b|libcurl\b)?\s*(\d[0-9.]*\d)?\b\s*(.*)$/i) { # Flag deprecation status. if($version ne "X" && $version ne "?") { if($1 && $1 ne $version) { print "error: $sym man page lists unmatching deprecation versions $version and $1\n"; $errcode++; } } else { $version = $1 || "?"; } $_ = $2; } } } } else { # Text line: accumulate. $line .= $_; } } close $fh; $$table{$sym} = $version; } } # Read symbols-in-versions. open(my $fh, "<", "$libdocdir/symbols-in-versions") || die "$libdocdir/symbols-in-versions"; while(<$fh>) { if($_ =~ /^((?:CURL|LIBCURL)\S+)\s+\S+\s*(\S*)\s*(\S*)$/) { if($3 eq "") { $syminver{$1} = "X"; if($2 ne "" && $2 ne ".") { $syminver{$1} = $2; } } } } close($fh); # Get header file names, opendir(my $dh, $incdir) || die "Can't opendir $incdir"; my @hfiles = grep { /\.h$/ } readdir($dh); closedir $dh; # Get functions and enum symbols from header files. for(@hfiles) { scan_header("$incdir/$_"); } # Get function statuses from man pages. foreach my $sym (keys %hdr) { if($sym =~/^(?:curl|curlx)_\w/) { scan_man_page("$libdocdir/$sym.3", $sym, \%funcman); } } # Get options from function man pages. scan_man_for_opts("$libdocdir/curl_easy_setopt.3", "CURLOPT"); scan_man_for_opts("$libdocdir/curl_easy_getinfo.3", "CURLINFO"); # Get deprecation status from option man pages. foreach my $sym (keys %syminver) { if($sym =~ /^(?:CURLOPT|CURLINFO)_\w+$/) { scan_man_page("$libdocdir/opts/$sym.3", $sym, \%optman); } } # Print results. my %keys = (%syminver, %funcman, %optman, %hdr); my $leader = <<HEADER Legend: <empty> Not listed X Not deprecated ? Deprecated in unknown version x.yy.z Deprecated in version x.yy.z Symbol symbols-in func man opt man .h -versions HEADER ; foreach my $sym (sort {$a cmp $b} keys %keys) { if($sym =~ /^(?:CURLOPT|CURLINFO|curl|curlx)_\w/) { my $s = exists($syminver{$sym})? $syminver{$sym}: " "; my $f = exists($funcman{$sym})? $funcman{$sym}: " "; my $o = exists($optman{$sym})? $optman{$sym}: " "; my $h = exists($hdr{$sym})? $hdr{$sym}: " "; my $r = " "; # There are deprecated symbols in symbols-in-versions that are aliases # and thus not listed anywhere else. Ignore them. "$f$o$h" =~ /[X ]{3}/ && next; # Check for inconsistencies between deprecations from the different sources. foreach my $k ($s, $f, $o, $h) { $r = $r eq " "? $k: $r; if($k ne " " && $r ne $k) { if($r eq "?") { $r = $k ne "X"? $k: "!"; } elsif($r eq "X" || $k ne "?") { $r = "!"; } } } if($r eq "!") { print $leader; $leader = ""; printf("%-38s %-11s %-9s %-9s %s\n", $sym, $s, $f, $o, $h); $errcode++; } } } exit $errcode;