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