1 #!/usr/bin/perl 2 #*************************************************************************** 3 # _ _ ____ _ 4 # Project ___| | | | _ \| | 5 # / __| | | | |_) | | 6 # | (__| |_| | _ <| |___ 7 # \___|\___/|_| \_\_____| 8 # 9 # Copyright (C) 2011 - 2016, Daniel Stenberg, <daniel (at] 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.haxx.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 ########################################################################### 23 24 my $max_column = 79; 25 my $indent = 2; 26 27 my $warnings; 28 my $errors; 29 my $supressed; # whitelisted problems 30 my $file; 31 my $dir="."; 32 my $wlist; 33 my $windows_os = $^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin'; 34 my $verbose; 35 my %whitelist; 36 37 my %warnings = ( 38 'LONGLINE' => "Line longer than $max_column", 39 'TABS' => 'TAB characters not allowed', 40 'TRAILINGSPACE' => 'Trailing white space on the line', 41 'CPPCOMMENTS' => '// comment detected', 42 'SPACEBEFOREPAREN' => 'space before an open parenthesis', 43 'SPACEAFTERPAREN' => 'space after open parenthesis', 44 'SPACEBEFORECLOSE' => 'space before a close parenthesis', 45 'SPACEBEFORECOMMA' => 'space before a comma', 46 'RETURNNOSPACE' => 'return without space', 47 'COMMANOSPACE' => 'comma without following space', 48 'BRACEELSE' => '} else on the same line', 49 'PARENBRACE' => '){ without sufficient space', 50 'SPACESEMILCOLON' => 'space before semicolon', 51 'BANNEDFUNC' => 'a banned function was used', 52 'FOPENMODE' => 'fopen needs a macro for the mode string', 53 'BRACEPOS' => 'wrong position for an open brace', 54 'INDENTATION' => 'wrong start column for code', 55 'COPYRIGHT' => 'file missing a copyright statement', 56 'BADCOMMAND' => 'bad !checksrc! instruction', 57 'UNUSEDIGNORE' => 'a warning ignore was not used', 58 'OPENCOMMENT' => 'file ended with a /* comment still "open"', 59 'ASTERISKSPACE' => 'pointer declared with space after asterisk', 60 'ASTERISKNOSPACE' => 'pointer declared without space before asterisk' 61 ); 62 63 sub readwhitelist { 64 open(W, "<$dir/checksrc.whitelist"); 65 my @all=<W>; 66 for(@all) { 67 $windows_os ? $_ =~ s/\r?\n$// : chomp; 68 $whitelist{$_}=1; 69 } 70 close(W); 71 } 72 73 sub checkwarn { 74 my ($name, $num, $col, $file, $line, $msg, $error) = @_; 75 76 my $w=$error?"error":"warning"; 77 my $nowarn=0; 78 79 #if(!$warnings{$name}) { 80 # print STDERR "Dev! there's no description for $name!\n"; 81 #} 82 83 # checksrc.whitelist 84 if($whitelist{$line}) { 85 $nowarn = 1; 86 } 87 # !checksrc! controlled 88 elsif($ignore{$name}) { 89 $ignore{$name}--; 90 $ignore_used{$name}++; 91 $nowarn = 1; 92 if(!$ignore{$name}) { 93 # reached zero, enable again 94 enable_warn($name, $line, $file, $l); 95 } 96 } 97 98 if($nowarn) { 99 $supressed++; 100 if($w) { 101 $swarnings++; 102 } 103 else { 104 $serrors++; 105 } 106 return; 107 } 108 109 if($w) { 110 $warnings++; 111 } 112 else { 113 $errors++; 114 } 115 116 $col++; 117 print "$file:$num:$col: $w: $msg ($name)\n"; 118 print " $line\n"; 119 120 if($col < 80) { 121 my $pref = (' ' x $col); 122 print "${pref}^\n"; 123 } 124 } 125 126 $file = shift @ARGV; 127 128 while(1) { 129 130 if($file =~ /-D(.*)/) { 131 $dir = $1; 132 $file = shift @ARGV; 133 next; 134 } 135 elsif($file =~ /-W(.*)/) { 136 $wlist .= " $1 "; 137 $file = shift @ARGV; 138 next; 139 } 140 elsif($file =~ /^(-h|--help)/) { 141 undef $file; 142 last; 143 } 144 145 last; 146 } 147 148 if(!$file) { 149 print "checksrc.pl [option] <file1> [file2] ...\n"; 150 print " Options:\n"; 151 print " -D[DIR] Directory to prepend file names\n"; 152 print " -h Show help output\n"; 153 print " -W[file] Whitelist the given file - ignore all its flaws\n"; 154 print "\nDetects and warns for these problems:\n"; 155 for(sort keys %warnings) { 156 printf (" %-18s: %s\n", $_, $warnings{$_}); 157 } 158 exit; 159 } 160 161 readwhitelist(); 162 163 do { 164 if("$wlist" !~ / $file /) { 165 my $fullname = $file; 166 $fullname = "$dir/$file" if ($fullname !~ '^\.?\.?/'); 167 scanfile($fullname); 168 } 169 $file = shift @ARGV; 170 171 } while($file); 172 173 sub checksrc_clear { 174 undef %ignore; 175 undef %ignore_set; 176 undef @ignore_line; 177 } 178 179 sub checksrc_endoffile { 180 my ($file) = @_; 181 for(keys %ignore_set) { 182 if($ignore_set{$_} && !$ignore_used{$_}) { 183 checkwarn("UNUSEDIGNORE", $ignore_set{$_}, 184 length($_)+11, $file, 185 $ignore_line[$ignore_set{$_}], 186 "Unused ignore: $_"); 187 } 188 } 189 } 190 191 sub enable_warn { 192 my ($what, $line, $file, $l) = @_; 193 194 # switch it back on, but warn if not triggered! 195 if(!$ignore_used{$what}) { 196 checkwarn("UNUSEDIGNORE", 197 $line, length($what) + 11, $file, $l, 198 "No warning was inhibited!"); 199 } 200 $ignore_set{$what}=0; 201 $ignore_used{$what}=0; 202 $ignore{$what}=0; 203 } 204 sub checksrc { 205 my ($cmd, $line, $file, $l) = @_; 206 if($cmd =~ / *([^ ]*) *(.*)/) { 207 my ($enable, $what) = ($1, $2); 208 $what =~ s: *\*/$::; # cut off end of C comment 209 # print "ENABLE $enable WHAT $what\n"; 210 if($enable eq "disable") { 211 my ($warn, $scope)=($1, $2); 212 if($what =~ /([^ ]*) +(.*)/) { 213 ($warn, $scope)=($1, $2); 214 } 215 else { 216 $warn = $what; 217 $scope = 1; 218 } 219 # print "IGNORE $warn for SCOPE $scope\n"; 220 if($scope eq "all") { 221 $scope=999999; 222 } 223 224 if($ignore_set{$warn}) { 225 checkwarn("BADCOMMAND", 226 $line, 0, $file, $l, 227 "$warn already disabled from line $ignore_set{$warn}"); 228 } 229 else { 230 $ignore{$warn}=$scope; 231 $ignore_set{$warn}=$line; 232 $ignore_line[$line]=$l; 233 } 234 } 235 elsif($enable eq "enable") { 236 enable_warn($what, $line, $file, $l); 237 } 238 else { 239 checkwarn("BADCOMMAND", 240 $line, 0, $file, $l, 241 "Illegal !checksrc! command"); 242 } 243 } 244 } 245 246 sub scanfile { 247 my ($file) = @_; 248 249 my $line = 1; 250 my $prevl; 251 my $l; 252 open(R, "<$file") || die "failed to open $file"; 253 254 my $incomment=0; 255 my $copyright=0; 256 checksrc_clear(); # for file based ignores 257 258 while(<R>) { 259 $windows_os ? $_ =~ s/\r?\n$// : chomp; 260 my $l = $_; 261 my $ol = $l; # keep the unmodified line for error reporting 262 my $column = 0; 263 264 # check for !checksrc! commands 265 if($l =~ /\!checksrc\! (.*)/) { 266 my $cmd = $1; 267 checksrc($cmd, $line, $file, $l) 268 } 269 270 # check for a copyright statement 271 if(!$copyright && ($l =~ /copyright .* \d\d\d\d/i)) { 272 $copyright=1; 273 } 274 275 # detect long lines 276 if(length($l) > $max_column) { 277 checkwarn("LONGLINE", $line, length($l), $file, $l, 278 "Longer than $max_column columns"); 279 } 280 # detect TAB characters 281 if($l =~ /^(.*)\t/) { 282 checkwarn("TABS", 283 $line, length($1), $file, $l, "Contains TAB character", 1); 284 } 285 # detect trailing white space 286 if($l =~ /^(.*)[ \t]+\z/) { 287 checkwarn("TRAILINGSPACE", 288 $line, length($1), $file, $l, "Trailing whitespace"); 289 } 290 291 # ------------------------------------------------------------ 292 # Above this marker, the checks were done on lines *including* 293 # comments 294 # ------------------------------------------------------------ 295 296 # strip off C89 comments 297 298 comment: 299 if(!$incomment) { 300 if($l =~ s/\/\*.*\*\// /g) { 301 # full /* comments */ were removed! 302 } 303 if($l =~ s/\/\*.*//) { 304 # start of /* comment was removed 305 $incomment = 1; 306 } 307 } 308 else { 309 if($l =~ s/.*\*\///) { 310 # end of comment */ was removed 311 $incomment = 0; 312 goto comment; 313 } 314 else { 315 # still within a comment 316 $l=""; 317 } 318 } 319 320 # ------------------------------------------------------------ 321 # Below this marker, the checks were done on lines *without* 322 # comments 323 # ------------------------------------------------------------ 324 325 # crude attempt to detect // comments without too many false 326 # positives 327 if($l =~ /^([^"\*]*)[^:"]\/\//) { 328 checkwarn("CPPCOMMENTS", 329 $line, length($1), $file, $l, "\/\/ comment"); 330 } 331 332 # check spaces after for/if/while 333 if($l =~ /^(.*)(for|if|while) \(/) { 334 if($1 =~ / *\#/) { 335 # this is a #if, treat it differently 336 } 337 else { 338 checkwarn("SPACEBEFOREPAREN", $line, length($1)+length($2), $file, $l, 339 "$2 with space"); 340 } 341 } 342 343 # check spaces after open parentheses 344 if($l =~ /^(.*[a-z])\( /i) { 345 checkwarn("SPACEAFTERPAREN", 346 $line, length($1)+1, $file, $l, 347 "space after open parenthesis"); 348 } 349 350 # check spaces before close parentheses, unless it was a space or a 351 # close parenthesis! 352 if($l =~ /(.*[^\) ]) \)/) { 353 checkwarn("SPACEBEFORECLOSE", 354 $line, length($1)+1, $file, $l, 355 "space before close parenthesis"); 356 } 357 358 # check spaces before comma! 359 if($l =~ /(.*[^ ]) ,/) { 360 checkwarn("SPACEBEFORECOMMA", 361 $line, length($1)+1, $file, $l, 362 "space before comma"); 363 } 364 365 # check for "return(" without space 366 if($l =~ /^(.*)return\(/) { 367 if($1 =~ / *\#/) { 368 # this is a #if, treat it differently 369 } 370 else { 371 checkwarn("RETURNNOSPACE", $line, length($1)+6, $file, $l, 372 "return without space before paren"); 373 } 374 } 375 376 # check for comma without space 377 if($l =~ /^(.*),[^ \n]/) { 378 my $pref=$1; 379 my $ign=0; 380 if($pref =~ / *\#/) { 381 # this is a #if, treat it differently 382 $ign=1; 383 } 384 elsif($pref =~ /\/\*/) { 385 # this is a comment 386 $ign=1; 387 } 388 elsif($pref =~ /[\"\']/) { 389 $ign = 1; 390 # There is a quote here, figure out whether the comma is 391 # within a string or '' or not. 392 if($pref =~ /\"/) { 393 # withing a string 394 } 395 elsif($pref =~ /\'$/) { 396 # a single letter 397 } 398 else { 399 $ign = 0; 400 } 401 } 402 if(!$ign) { 403 checkwarn("COMMANOSPACE", $line, length($pref)+1, $file, $l, 404 "comma without following space"); 405 } 406 } 407 408 # check for "} else" 409 if($l =~ /^(.*)\} *else/) { 410 checkwarn("BRACEELSE", 411 $line, length($1), $file, $l, "else after closing brace on same line"); 412 } 413 # check for "){" 414 if($l =~ /^(.*)\)\{/) { 415 checkwarn("PARENBRACE", 416 $line, length($1)+1, $file, $l, "missing space after close paren"); 417 } 418 419 # check for space before the semicolon last in a line 420 if($l =~ /^(.*[^ ].*) ;$/) { 421 checkwarn("SPACESEMILCOLON", 422 $line, length($1), $file, $ol, "space before last semicolon"); 423 } 424 425 # scan for use of banned functions 426 if($l =~ /^(.*\W) 427 (gets| 428 strtok| 429 v?sprintf| 430 (str|_mbs|_tcs|_wcs)n?cat| 431 LoadLibrary(Ex)?(A|W)?) 432 \s*\( 433 /x) { 434 checkwarn("BANNEDFUNC", 435 $line, length($1), $file, $ol, 436 "use of $2 is banned"); 437 } 438 439 # scan for use of non-binary fopen without the macro 440 if($l =~ /^(.*\W)fopen\s*\([^,]*, *\"([^"]*)/) { 441 my $mode = $2; 442 if($mode !~ /b/) { 443 checkwarn("FOPENMODE", 444 $line, length($1), $file, $ol, 445 "use of non-binary fopen without FOPEN_* macro: $mode"); 446 } 447 } 448 449 # check for open brace first on line but not first column 450 # only alert if previous line ended with a close paren and wasn't a cpp 451 # line 452 if((($prevl =~ /\)\z/) && ($prevl !~ /^ *#/)) && ($l =~ /^( +)\{/)) { 453 checkwarn("BRACEPOS", 454 $line, length($1), $file, $ol, "badly placed open brace"); 455 } 456 457 # if the previous line starts with if/while/for AND ends with an open 458 # brace, check that this line is indented $indent more steps, if not 459 # a cpp line 460 if($prevl =~ /^( *)(if|while|for)\(.*\{\z/) { 461 my $first = length($1); 462 463 # this line has some character besides spaces 464 if(($l !~ /^ *#/) && ($l =~ /^( *)[^ ]/)) { 465 my $second = length($1); 466 my $expect = $first+$indent; 467 if($expect != $second) { 468 my $diff = $second - $first; 469 checkwarn("INDENTATION", $line, length($1), $file, $ol, 470 "not indented $indent steps, uses $diff)"); 471 472 } 473 } 474 } 475 476 # check for 'char * name' 477 if(($l =~ /(^.*(char|int|long|void|curl_slist|CURL|CURLM|CURLMsg|curl_httppost) *(\*+)) (\w+)/) && ($4 ne "const")) { 478 checkwarn("ASTERISKNOSPACE", 479 $line, length($1), $file, $ol, 480 "no space after declarative asterisk"); 481 } 482 # check for 'char*' 483 if(($l =~ /(^.*(char|int|long|void|curl_slist|CURL|CURLM|CURLMsg|curl_httppost|sockaddr_in|FILE)\*)/)) { 484 checkwarn("ASTERISKNOSPACE", 485 $line, length($1)-1, $file, $ol, 486 "no space before asterisk"); 487 } 488 489 # check for 'void func() {', but avoid false positives by requiring 490 # both an open and closed parentheses before the open brace 491 if($l =~ /^((\w).*){\z/) { 492 my $k = $1; 493 $k =~ s/const *//; 494 $k =~ s/static *//; 495 if($k =~ /\(.*\)/) { 496 checkwarn("BRACEPOS", 497 $line, length($l)-1, $file, $ol, 498 "wrongly placed open brace"); 499 } 500 } 501 $line++; 502 $prevl = $ol; 503 } 504 505 if(!$copyright) { 506 checkwarn("COPYRIGHT", 1, 0, $file, "", "Missing copyright statement", 1); 507 } 508 if($incomment) { 509 checkwarn("OPENCOMMENT", 1, 0, $file, "", "Missing closing comment", 1); 510 } 511 512 checksrc_endoffile($file); 513 514 close(R); 515 516 } 517 518 519 if($errors || $warnings || $verbose) { 520 printf "checksrc: %d errors and %d warnings\n", $errors, $warnings; 521 if($supressed) { 522 printf "checksrc: %d errors and %d warnings suppressed\n", 523 $serrors, 524 $swarnings; 525 } 526 exit 5; # return failure 527 } 528