1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 10# 11# This software is licensed as described in the file COPYING, which 12# you should have received as part of this distribution. The terms 13# are also available at https://curl.se/docs/copyright.html. 14# 15# You may opt to use, copy, modify, merge, publish, distribute and/or sell 16# copies of the Software, and permit persons to whom the Software is 17# furnished to do so, under the terms of the COPYING file. 18# 19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20# KIND, either express or implied. 21# 22# SPDX-License-Identifier: curl 23# 24########################################################################### 25# 26# This script grew out of help from Przemyslaw Iskra and Balint Szilakszi 27# a late evening in the #curl IRC channel. 28# 29 30use strict; 31use warnings; 32use vars qw($Cpreprocessor); 33 34# 35# configurehelp perl module is generated by configure script 36# 37my $rc = eval { 38 require configurehelp; 39 configurehelp->import(qw( 40 $Cpreprocessor 41 )); 42 1; 43}; 44# Set default values if configure has not generated a configurehelp.pm file. 45# This is the case with cmake. 46if (!$rc) { 47 $Cpreprocessor = 'cpp'; 48} 49 50# we may get the dir root pointed out 51my $root=$ARGV[0] || "."; 52 53# need an include directory when building out-of-tree 54my $i = ($ARGV[1]) ? "-I$ARGV[1] " : ''; 55 56my $verbose=0; 57my $summary=0; 58my $misses=0; 59 60my @manrefs; 61my @syms; 62my %doc; 63my %rem; 64 65# scanenum runs the preprocessor on curl.h so it will process all enums 66# included by it, which *should* be all headers 67sub scanenum { 68 my ($file) = @_; 69 open my $h_in, "-|", "$Cpreprocessor $i$file" || die "Cannot preprocess $file"; 70 while ( <$h_in> ) { 71 if ( /enum\s+(\S+\s+)?{/ .. /}/ ) { 72 s/^\s+//; 73 next unless /^CURL/; 74 chomp; 75 s/[,\s].*//; 76 push @syms, $_; 77 } 78 } 79 close $h_in || die "Error preprocessing $file"; 80} 81 82sub scanheader { 83 my ($f)=@_; 84 open my $h, "<", "$f"; 85 while(<$h>) { 86 if (/^#define ((LIB|)CURL[A-Za-z0-9_]*)/) { 87 push @syms, $1; 88 } 89 } 90 close $h; 91} 92 93sub scanallheaders { 94 my $d = "$root/include/curl"; 95 opendir(my $dh, $d) || 96 die "Can't opendir: $!"; 97 my @headers = grep { /.h\z/ } readdir($dh); 98 closedir $dh; 99 foreach my $h (@headers) { 100 scanenum("$d/$h"); 101 scanheader("$d/$h"); 102 } 103} 104 105sub checkmanpage { 106 my ($m) = @_; 107 108 open(my $mh, "<", "$m"); 109 my $line = 1; 110 while(<$mh>) { 111 # strip off formatting 112 $_ =~ s/(^|[^A-Z0-9])[*_]+/ /; 113 # detect global-looking 'CURL[BLABLA]_*' symbols 114 while(s/\W(CURL(AUTH|E|H|MOPT|OPT|SHOPT|UE|M|SSH|SSLBACKEND|HEADER|FORM|FTP|PIPE|MIMEOPT|GSSAPI|ALTSVC|PROTO|PROXY|UPART|USESSL|_READFUNC|_WRITEFUNC|_CSELECT|_FORMADD|_IPRESOLVE|_REDIR|_RTSPREQ|_TIMECOND|_VERSION)_[a-zA-Z0-9_]+)//) { 115 my $s = $1; 116 # skip two "special" ones 117 if($s !~ /^(CURLE_OBSOLETE|CURLOPT_TEMPLATE)/) { 118 push @manrefs, "$1:$m:$line"; 119 } 120 } 121 $line++; 122 } 123 close($mh); 124} 125 126sub scanman_md_dir { 127 my ($d) = @_; 128 opendir(my $dh, $d) || 129 die "Can't opendir: $!"; 130 my @mans = grep { /.md\z/ } readdir($dh); 131 closedir $dh; 132 for my $m (@mans) { 133 checkmanpage("$d/$m"); 134 } 135} 136 137 138scanallheaders(); 139scanman_md_dir("$root/docs/libcurl"); 140scanman_md_dir("$root/docs/libcurl/opts"); 141 142open my $s, "<", "$root/docs/libcurl/symbols-in-versions"; 143while(<$s>) { 144 if(/(^[^ \n]+) +(.*)/) { 145 my ($sym, $rest)=($1, $2); 146 if($doc{$sym}) { 147 print "Detected duplicate symbol: $sym\n"; 148 $misses++; 149 next; 150 } 151 $doc{$sym}=$sym; 152 my @a=split(/ +/, $rest); 153 if($a[2]) { 154 # this symbol is documented to have been present the last time 155 # in this release 156 $rem{$sym}=$a[2]; 157 } 158 } 159} 160close $s; 161 162my $ignored=0; 163for my $e (sort @syms) { 164 # OBSOLETE - names that are just placeholders for a position where we 165 # previously had a name, that is now removed. The OBSOLETE names should 166 # never be used for anything. 167 # 168 # CURL_EXTERN - is a define used for libcurl functions that are external, 169 # public. No app or other code should ever use it. 170 # 171 # CURLINC_ - defines for header dual-include prevention, ignore those. 172 # 173 # CURL_TEMP_ - are defined and *undefined* again within the file 174 # 175 # *_LAST and *_LASTENTRY are just prefix for the placeholders used for the 176 # last entry in many enum series. 177 # 178 179 if($e =~ /(OBSOLETE|^CURL_EXTERN|^CURLINC_|_LAST\z|_LASTENTRY\z|^CURL_TEMP_)/) { 180 $ignored++; 181 next; 182 } 183 if($doc{$e}) { 184 if($verbose) { 185 print $e."\n"; 186 } 187 $doc{$e}="used"; 188 next; 189 } 190 else { 191 print $e."\n"; 192 $misses++; 193 } 194} 195 196# 197# now scan through all symbols that were present in the symbols-in-versions 198# but not in the headers 199# 200# If the symbols were marked 'removed' in symbols-in-versions we don't output 201# anything about it since that is perfectly fine. 202# 203 204my $anyremoved; 205 206for my $e (sort keys %doc) { 207 if(($doc{$e} ne "used") && !$rem{$e}) { 208 209 if(!$anyremoved++) { 210 print "Missing symbols mentioned in symbols-in-versions\n"; 211 print "Add them to a header, or mark them as removed.\n"; 212 } 213 214 print "$e\n"; 215 $misses++; 216 } 217} 218 219my %warned; 220for my $r (@manrefs) { 221 if($r =~ /^([^:]+):(.*)/) { 222 my ($sym, $file)=($1, $2); 223 if(!$doc{$sym} && !$warned{$sym, $file}) { 224 print "$file: $sym is not a public symbol\n"; 225 $warned{$sym, $file} = 1; 226 } 227 } 228} 229 230if($summary) { 231 print "Summary:\n"; 232 printf "%d symbols in headers (out of which %d are ignored)\n", scalar(@syms), 233 $ignored; 234 printf "%d symbols in headers are interesting\n", 235 scalar(@syms)- $ignored; 236 printf "%d symbols are listed in symbols-in-versions\n (out of which %d are listed as removed)\n", scalar(keys %doc), scalar(keys %rem); 237 printf "%d symbols in symbols-in-versions should match the ones in headers\n", scalar(keys %doc) - scalar(keys %rem); 238} 239 240if($misses) { 241 exit 0; # there are stuff to attend to! 242} 243else { 244 print "OK\n"; 245} 246