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 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 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(M, "<$m"); 109 my $line = 1; 110 while(<M>) { 111 # strip off formatting 112 $_ =~ s/\\f[BPRI]//; 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(M); 124} 125 126sub scanman3dir { 127 my ($d) = @_; 128 opendir(my $dh, $d) || 129 die "Can't opendir: $!"; 130 my @mans = grep { /.3\z/ } readdir($dh); 131 closedir $dh; 132 for my $m (@mans) { 133 checkmanpage("$d/$m"); 134 } 135} 136 137 138scanallheaders(); 139scanman3dir("$root/docs/libcurl"); 140scanman3dir("$root/docs/libcurl/opts"); 141 142open 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 # *_LAST and *_LASTENTRY are just prefix for the placeholders used for the 174 # last entry in many enum series. 175 # 176 177 if($e =~ /(OBSOLETE|^CURL_EXTERN|^CURLINC_|_LAST\z|_LASTENTRY\z)/) { 178 $ignored++; 179 next; 180 } 181 if($doc{$e}) { 182 if($verbose) { 183 print $e."\n"; 184 } 185 $doc{$e}="used"; 186 next; 187 } 188 else { 189 print $e."\n"; 190 $misses++; 191 } 192} 193 194# 195# now scan through all symbols that were present in the symbols-in-versions 196# but not in the headers 197# 198# If the symbols were marked 'removed' in symbols-in-versions we don't output 199# anything about it since that is perfectly fine. 200# 201 202my $anyremoved; 203 204for my $e (sort keys %doc) { 205 if(($doc{$e} ne "used") && !$rem{$e}) { 206 207 if(!$anyremoved++) { 208 print "Missing symbols mentioned in symbols-in-versions\n"; 209 print "Add them to a header, or mark them as removed.\n"; 210 } 211 212 print "$e\n"; 213 $misses++; 214 } 215} 216 217my %warned; 218for my $r (@manrefs) { 219 if($r =~ /^([^:]+):(.*)/) { 220 my ($sym, $file)=($1, $2); 221 if(!$doc{$sym} && !$warned{$sym, $file}) { 222 print "$file: $sym is not a public symbol\n"; 223 $warned{$sym, $file} = 1; 224 } 225 } 226} 227 228if($summary) { 229 print "Summary:\n"; 230 printf "%d symbols in headers (out of which %d are ignored)\n", scalar(@syms), 231 $ignored; 232 printf "%d symbols in headers are interesting\n", 233 scalar(@syms)- $ignored; 234 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); 235 printf "%d symbols in symbols-in-versions should match the ones in headers\n", scalar(keys %doc) - scalar(keys %rem); 236} 237 238if($misses) { 239 exit 0; # there are stuff to attend to! 240} 241else { 242 print "OK\n"; 243} 244