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