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 26use strict; 27use warnings; 28 29my $max_column = 79; 30my $indent = 2; 31 32my $warnings = 0; 33my $swarnings = 0; 34my $errors = 0; 35my $serrors = 0; 36my $suppressed; # skipped problems 37my $file; 38my $dir="."; 39my $wlist=""; 40my @alist; 41my $windows_os = $^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys'; 42my $verbose; 43my %skiplist; 44 45my %ignore; 46my %ignore_set; 47my %ignore_used; 48my @ignore_line; 49 50my %warnings_extended = ( 51 'COPYRIGHTYEAR' => 'copyright year incorrect', 52 'STRERROR', => 'strerror() detected', 53 'STRNCPY', => 'strncpy() detected', 54 'STDERR', => 'stderr detected', 55 ); 56 57my %warnings = ( 58 'ASSIGNWITHINCONDITION' => 'assignment within conditional expression', 59 'ASTERISKNOSPACE' => 'pointer declared without space before asterisk', 60 'ASTERISKSPACE' => 'pointer declared with space after asterisk', 61 'BADCOMMAND' => 'bad !checksrc! instruction', 62 'BANNEDFUNC' => 'a banned function was used', 63 'BANNEDPREPROC' => 'a banned symbol was used on a preprocessor line', 64 'BRACEELSE' => '} else on the same line', 65 'BRACEPOS' => 'wrong position for an open brace', 66 'BRACEWHILE' => 'A single space between open brace and while', 67 'COMMANOSPACE' => 'comma without following space', 68 'COMMENTNOSPACEEND' => 'no space before */', 69 'COMMENTNOSPACESTART' => 'no space following /*', 70 'COPYRIGHT' => 'file missing a copyright statement', 71 'CPPCOMMENTS' => '// comment detected', 72 'DOBRACE' => 'A single space between do and open brace', 73 'EMPTYLINEBRACE' => 'Empty line before the open brace', 74 'EQUALSNOSPACE' => 'equals sign without following space', 75 'EQUALSNULL' => 'if/while comparison with == NULL', 76 'EXCLAMATIONSPACE' => 'Whitespace after exclamation mark in expression', 77 'FOPENMODE' => 'fopen needs a macro for the mode string', 78 'INCLUDEDUP', => 'same file is included again', 79 'INDENTATION' => 'wrong start column for code', 80 'LONGLINE' => "Line longer than $max_column", 81 'SPACEBEFORELABEL' => 'labels not at the start of the line', 82 'MULTISPACE' => 'multiple spaces used when not suitable', 83 'NOSPACEAND' => 'missing space around Logical AND operator', 84 'NOSPACEC' => 'missing space around ternary colon operator', 85 'NOSPACEEQUALS' => 'equals sign without preceding space', 86 'NOSPACEQ' => 'missing space around ternary question mark operator', 87 'NOSPACETHAN' => 'missing space around less or greater than', 88 'NOTEQUALSZERO', => 'if/while comparison with != 0', 89 'ONELINECONDITION' => 'conditional block on the same line as the if()', 90 'OPENCOMMENT' => 'file ended with a /* comment still "open"', 91 'PARENBRACE' => '){ without sufficient space', 92 'RETURNNOSPACE' => 'return without space', 93 'SEMINOSPACE' => 'semicolon without following space', 94 'SIZEOFNOPAREN' => 'use of sizeof without parentheses', 95 'SNPRINTF' => 'use of snprintf', 96 'SPACEAFTERPAREN' => 'space after open parenthesis', 97 'SPACEBEFORECLOSE' => 'space before a close parenthesis', 98 'SPACEBEFORECOMMA' => 'space before a comma', 99 'SPACEBEFOREPAREN' => 'space before an open parenthesis', 100 'SPACESEMICOLON' => 'space before semicolon', 101 'SPACESWITCHCOLON' => 'space before colon of switch label', 102 'TABS' => 'TAB characters not allowed', 103 'TRAILINGSPACE' => 'Trailing whitespace on the line', 104 'TYPEDEFSTRUCT' => 'typedefed struct', 105 'UNUSEDIGNORE' => 'a warning ignore was not used', 106 ); 107 108sub readskiplist { 109 open(my $W, '<', "$dir/checksrc.skip") or return; 110 my @all=<$W>; 111 for(@all) { 112 $windows_os ? $_ =~ s/\r?\n$// : chomp; 113 $skiplist{$_}=1; 114 } 115 close($W); 116} 117 118# Reads the .checksrc in $dir for any extended warnings to enable locally. 119# Currently there is no support for disabling warnings from the standard set, 120# and since that's already handled via !checksrc! commands there is probably 121# little use to add it. 122sub readlocalfile { 123 my ($file) = @_; 124 my $i = 0; 125 my $rcfile; 126 127 if(($dir eq ".") && $file =~ /\//) { 128 my $ldir; 129 if($file =~ /(.*)\//) { 130 $ldir = $1; 131 open($rcfile, "<", "$dir/$ldir/.checksrc") or return; 132 } 133 } 134 else { 135 open($rcfile, "<", "$dir/.checksrc") or return; 136 } 137 138 while(<$rcfile>) { 139 $windows_os ? $_ =~ s/\r?\n$// : chomp; 140 $i++; 141 142 # Lines starting with '#' are considered comments 143 if (/^\s*(#.*)/) { 144 next; 145 } 146 elsif (/^\s*enable ([A-Z]+)$/) { 147 if(!defined($warnings_extended{$1})) { 148 print STDERR "invalid warning specified in .checksrc: \"$1\"\n"; 149 next; 150 } 151 $warnings{$1} = $warnings_extended{$1}; 152 } 153 elsif (/^\s*disable ([A-Z]+)$/) { 154 if(!defined($warnings{$1})) { 155 print STDERR "invalid warning specified in .checksrc: \"$1\"\n"; 156 next; 157 } 158 # Accept-list 159 push @alist, $1; 160 } 161 else { 162 die "Invalid format in $dir/.checksrc on line $i\n"; 163 } 164 } 165 close($rcfile); 166} 167 168sub checkwarn { 169 my ($name, $num, $col, $file, $line, $msg, $error) = @_; 170 171 my $w=$error?"error":"warning"; 172 my $nowarn=0; 173 174 #if(!$warnings{$name}) { 175 # print STDERR "Dev! there's no description for $name!\n"; 176 #} 177 178 # checksrc.skip 179 if($skiplist{$line}) { 180 $nowarn = 1; 181 } 182 # !checksrc! controlled 183 elsif($ignore{$name}) { 184 $ignore{$name}--; 185 $ignore_used{$name}++; 186 $nowarn = 1; 187 if(!$ignore{$name}) { 188 # reached zero, enable again 189 enable_warn($name, $num, $file, $line); 190 } 191 } 192 193 if($nowarn) { 194 $suppressed++; 195 if($w) { 196 $swarnings++; 197 } 198 else { 199 $serrors++; 200 } 201 return; 202 } 203 204 if($w) { 205 $warnings++; 206 } 207 else { 208 $errors++; 209 } 210 211 $col++; 212 print "$file:$num:$col: $w: $msg ($name)\n"; 213 print " $line\n"; 214 215 if($col < 80) { 216 my $pref = (' ' x $col); 217 print "${pref}^\n"; 218 } 219} 220 221$file = shift @ARGV; 222 223while(defined $file) { 224 225 if($file =~ /-D(.*)/) { 226 $dir = $1; 227 $file = shift @ARGV; 228 next; 229 } 230 elsif($file =~ /-W(.*)/) { 231 $wlist .= " $1 "; 232 $file = shift @ARGV; 233 next; 234 } 235 elsif($file =~ /-A(.+)/) { 236 push @alist, $1; 237 $file = shift @ARGV; 238 next; 239 } 240 elsif($file =~ /-i([1-9])/) { 241 $indent = $1 + 0; 242 $file = shift @ARGV; 243 next; 244 } 245 elsif($file =~ /-m([0-9]+)/) { 246 $max_column = $1 + 0; 247 $file = shift @ARGV; 248 next; 249 } 250 elsif($file =~ /^(-h|--help)/) { 251 undef $file; 252 last; 253 } 254 255 last; 256} 257 258if(!$file) { 259 print "checksrc.pl [option] <file1> [file2] ...\n"; 260 print " Options:\n"; 261 print " -A[rule] Accept this violation, can be used multiple times\n"; 262 print " -D[DIR] Directory to prepend file names\n"; 263 print " -h Show help output\n"; 264 print " -W[file] Skip the given file - ignore all its flaws\n"; 265 print " -i<n> Indent spaces. Default: 2\n"; 266 print " -m<n> Maximum line length. Default: 79\n"; 267 print "\nDetects and warns for these problems:\n"; 268 my @allw = keys %warnings; 269 push @allw, keys %warnings_extended; 270 for my $w (sort @allw) { 271 if($warnings{$w}) { 272 printf (" %-18s: %s\n", $w, $warnings{$w}); 273 } 274 else { 275 printf (" %-18s: %s[*]\n", $w, $warnings_extended{$w}); 276 } 277 } 278 print " [*] = disabled by default\n"; 279 exit; 280} 281 282readskiplist(); 283readlocalfile($file); 284 285do { 286 if("$wlist" !~ / $file /) { 287 my $fullname = $file; 288 $fullname = "$dir/$file" if ($fullname !~ '^\.?\.?/'); 289 scanfile($fullname); 290 } 291 $file = shift @ARGV; 292 293} while($file); 294 295sub accept_violations { 296 for my $r (@alist) { 297 if(!$warnings{$r}) { 298 print "'$r' is not a warning to accept!\n"; 299 exit; 300 } 301 $ignore{$r}=999999; 302 $ignore_used{$r}=0; 303 } 304} 305 306sub checksrc_clear { 307 undef %ignore; 308 undef %ignore_set; 309 undef @ignore_line; 310} 311 312sub checksrc_endoffile { 313 my ($file) = @_; 314 for(keys %ignore_set) { 315 if($ignore_set{$_} && !$ignore_used{$_}) { 316 checkwarn("UNUSEDIGNORE", $ignore_set{$_}, 317 length($_)+11, $file, 318 $ignore_line[$ignore_set{$_}], 319 "Unused ignore: $_"); 320 } 321 } 322} 323 324sub enable_warn { 325 my ($what, $line, $file, $l) = @_; 326 327 # switch it back on, but warn if not triggered! 328 if(!$ignore_used{$what}) { 329 checkwarn("UNUSEDIGNORE", 330 $line, length($what) + 11, $file, $l, 331 "No warning was inhibited!"); 332 } 333 $ignore_set{$what}=0; 334 $ignore_used{$what}=0; 335 $ignore{$what}=0; 336} 337sub checksrc { 338 my ($cmd, $line, $file, $l) = @_; 339 if($cmd =~ / *([^ ]*) *(.*)/) { 340 my ($enable, $what) = ($1, $2); 341 $what =~ s: *\*/$::; # cut off end of C comment 342 # print "ENABLE $enable WHAT $what\n"; 343 if($enable eq "disable") { 344 my ($warn, $scope)=($1, $2); 345 if($what =~ /([^ ]*) +(.*)/) { 346 ($warn, $scope)=($1, $2); 347 } 348 else { 349 $warn = $what; 350 $scope = 1; 351 } 352 # print "IGNORE $warn for SCOPE $scope\n"; 353 if($scope eq "all") { 354 $scope=999999; 355 } 356 357 # Comparing for a literal zero rather than the scalar value zero 358 # covers the case where $scope contains the ending '*' from the 359 # comment. If we use a scalar comparison (==) we induce warnings 360 # on non-scalar contents. 361 if($scope eq "0") { 362 checkwarn("BADCOMMAND", 363 $line, 0, $file, $l, 364 "Disable zero not supported, did you mean to enable?"); 365 } 366 elsif($ignore_set{$warn}) { 367 checkwarn("BADCOMMAND", 368 $line, 0, $file, $l, 369 "$warn already disabled from line $ignore_set{$warn}"); 370 } 371 else { 372 $ignore{$warn}=$scope; 373 $ignore_set{$warn}=$line; 374 $ignore_line[$line]=$l; 375 } 376 } 377 elsif($enable eq "enable") { 378 enable_warn($what, $line, $file, $l); 379 } 380 else { 381 checkwarn("BADCOMMAND", 382 $line, 0, $file, $l, 383 "Illegal !checksrc! command"); 384 } 385 } 386} 387 388sub nostrings { 389 my ($str) = @_; 390 $str =~ s/\".*\"//g; 391 return $str; 392} 393 394sub scanfile { 395 my ($file) = @_; 396 397 my $line = 1; 398 my $prevl=""; 399 my $prevpl=""; 400 my $l = ""; 401 my $prep = 0; 402 my $prevp = 0; 403 open(my $R, '<', $file) || die "failed to open $file"; 404 405 my $incomment=0; 406 my @copyright=(); 407 my %includes; 408 checksrc_clear(); # for file based ignores 409 accept_violations(); 410 411 while(<$R>) { 412 $windows_os ? $_ =~ s/\r?\n$// : chomp; 413 my $l = $_; 414 my $ol = $l; # keep the unmodified line for error reporting 415 my $column = 0; 416 417 # check for !checksrc! commands 418 if($l =~ /\!checksrc\! (.*)/) { 419 my $cmd = $1; 420 checksrc($cmd, $line, $file, $l) 421 } 422 423 if($l =~ /^#line (\d+) \"([^\"]*)\"/) { 424 # a #line instruction 425 $file = $2; 426 $line = $1; 427 next; 428 } 429 430 # check for a copyright statement and save the years 431 if($l =~ /\* +copyright .* (\d\d\d\d|)/i) { 432 my $count = 0; 433 while($l =~ /([\d]{4})/g) { 434 push @copyright, { 435 year => $1, 436 line => $line, 437 col => index($l, $1), 438 code => $l 439 }; 440 $count++; 441 } 442 if(!$count) { 443 # year-less 444 push @copyright, { 445 year => -1, 446 line => $line, 447 col => index($l, $1), 448 code => $l 449 }; 450 } 451 } 452 453 # detect long lines 454 if(length($l) > $max_column) { 455 checkwarn("LONGLINE", $line, length($l), $file, $l, 456 "Longer than $max_column columns"); 457 } 458 # detect TAB characters 459 if($l =~ /^(.*)\t/) { 460 checkwarn("TABS", 461 $line, length($1), $file, $l, "Contains TAB character", 1); 462 } 463 # detect trailing whitespace 464 if($l =~ /^(.*)[ \t]+\z/) { 465 checkwarn("TRAILINGSPACE", 466 $line, length($1), $file, $l, "Trailing whitespace"); 467 } 468 469 # no space after comment start 470 if($l =~ /^(.*)\/\*\w/) { 471 checkwarn("COMMENTNOSPACESTART", 472 $line, length($1) + 2, $file, $l, 473 "Missing space after comment start"); 474 } 475 # no space at comment end 476 if($l =~ /^(.*)\w\*\//) { 477 checkwarn("COMMENTNOSPACEEND", 478 $line, length($1) + 1, $file, $l, 479 "Missing space end comment end"); 480 } 481 # ------------------------------------------------------------ 482 # Above this marker, the checks were done on lines *including* 483 # comments 484 # ------------------------------------------------------------ 485 486 # strip off C89 comments 487 488 comment: 489 if(!$incomment) { 490 if($l =~ s/\/\*.*\*\// /g) { 491 # full /* comments */ were removed! 492 } 493 if($l =~ s/\/\*.*//) { 494 # start of /* comment was removed 495 $incomment = 1; 496 } 497 } 498 else { 499 if($l =~ s/.*\*\///) { 500 # end of comment */ was removed 501 $incomment = 0; 502 goto comment; 503 } 504 else { 505 # still within a comment 506 $l=""; 507 } 508 } 509 510 # ------------------------------------------------------------ 511 # Below this marker, the checks were done on lines *without* 512 # comments 513 # ------------------------------------------------------------ 514 515 # prev line was a preprocessor **and** ended with a backslash 516 if($prep && ($prevpl =~ /\\ *\z/)) { 517 # this is still a preprocessor line 518 $prep = 1; 519 goto preproc; 520 } 521 $prep = 0; 522 523 # crude attempt to detect // comments without too many false 524 # positives 525 if($l =~ /^(([^"\*]*)[^:"]|)\/\//) { 526 checkwarn("CPPCOMMENTS", 527 $line, length($1), $file, $l, "\/\/ comment"); 528 } 529 530 if($l =~ /^(\#\s*include\s+)([\">].*[>}"])/) { 531 my ($pre, $path) = ($1, $2); 532 if($includes{$path}) { 533 checkwarn("INCLUDEDUP", 534 $line, length($1), $file, $l, "duplicated include"); 535 } 536 $includes{$path} = $l; 537 } 538 539 # detect and strip preprocessor directives 540 if($l =~ /^[ \t]*\#/) { 541 # preprocessor line 542 $prep = 1; 543 goto preproc; 544 } 545 546 my $nostr = nostrings($l); 547 # check spaces after for/if/while/function call 548 if($nostr =~ /^(.*)(for|if|while|switch| ([a-zA-Z0-9_]+)) \((.)/) { 549 my ($leading, $word, $extra, $first)=($1,$2,$3,$4); 550 if($1 =~ / *\#/) { 551 # this is a #if, treat it differently 552 } 553 elsif(defined $3 && $3 eq "return") { 554 # return must have a space 555 } 556 elsif(defined $3 && $3 eq "case") { 557 # case must have a space 558 } 559 elsif(($first eq "*") && ($word !~ /(for|if|while|switch)/)) { 560 # A "(*" beginning makes the space OK because it wants to 561 # allow function pointer declared 562 } 563 elsif($1 =~ / *typedef/) { 564 # typedefs can use space-paren 565 } 566 else { 567 checkwarn("SPACEBEFOREPAREN", $line, length($leading)+length($word), $file, $l, 568 "$word with space"); 569 } 570 } 571 # check for '== NULL' in if/while conditions but not if the thing on 572 # the left of it is a function call 573 if($nostr =~ /^(.*)(if|while)(\(.*?)([!=]= NULL|NULL [!=]=)/) { 574 checkwarn("EQUALSNULL", $line, 575 length($1) + length($2) + length($3), 576 $file, $l, "we prefer !variable instead of \"== NULL\" comparisons"); 577 } 578 579 # check for '!= 0' in if/while conditions but not if the thing on 580 # the left of it is a function call 581 if($nostr =~ /^(.*)(if|while)(\(.*[^)]) != 0[^x]/) { 582 checkwarn("NOTEQUALSZERO", $line, 583 length($1) + length($2) + length($3), 584 $file, $l, "we prefer if(rc) instead of \"rc != 0\" comparisons"); 585 } 586 587 # check spaces in 'do {' 588 if($nostr =~ /^( *)do( *)\{/ && length($2) != 1) { 589 checkwarn("DOBRACE", $line, length($1) + 2, $file, $l, "one space after do before brace"); 590 } 591 # check spaces in 'do {' 592 elsif($nostr =~ /^( *)\}( *)while/ && length($2) != 1) { 593 checkwarn("BRACEWHILE", $line, length($1) + 2, $file, $l, "one space between brace and while"); 594 } 595 if($nostr =~ /^((.*\s)(if) *\()(.*)\)(.*)/) { 596 my $pos = length($1); 597 my $postparen = $5; 598 my $cond = $4; 599 if($cond =~ / = /) { 600 checkwarn("ASSIGNWITHINCONDITION", 601 $line, $pos+1, $file, $l, 602 "assignment within conditional expression"); 603 } 604 my $temp = $cond; 605 $temp =~ s/\(//g; # remove open parens 606 my $openc = length($cond) - length($temp); 607 608 $temp = $cond; 609 $temp =~ s/\)//g; # remove close parens 610 my $closec = length($cond) - length($temp); 611 my $even = $openc == $closec; 612 613 if($l =~ / *\#/) { 614 # this is a #if, treat it differently 615 } 616 elsif($even && $postparen && 617 ($postparen !~ /^ *$/) && ($postparen !~ /^ *[,{&|\\]+/)) { 618 checkwarn("ONELINECONDITION", 619 $line, length($l)-length($postparen), $file, $l, 620 "conditional block on the same line"); 621 } 622 } 623 # check spaces after open parentheses 624 if($l =~ /^(.*[a-z])\( /i) { 625 checkwarn("SPACEAFTERPAREN", 626 $line, length($1)+1, $file, $l, 627 "space after open parenthesis"); 628 } 629 630 # check spaces before Logical AND operator 631 if($nostr =~ /^(.*)\w&&/i) { 632 checkwarn("NOSPACEAND", 633 $line, length($1)+1, $file, $l, 634 "missing space before Logical AND"); 635 } 636 637 # check spaces after Logical AND operator 638 if($nostr =~ /^(.*&&)\w/i) { 639 checkwarn("NOSPACEAND", 640 $line, length($1), $file, $l, 641 "missing space after Logical AND"); 642 } 643 644 # check spaces before colon 645 if($nostr =~ /^(.*[^']\?[^'].*)(\w|\)|\]|')\:/i) { 646 my $m = $1; 647 my $e = $nostr; 648 $e =~ s/'(.)':'(.)'/$1:$2/g; # eliminate chars quotes that surround colon 649 $e =~ s/':'//g; # ignore these 650 if($e =~ /^(.*[^']\?[^'].*)(\w|\)|\]|')\:/i) { 651 checkwarn("NOSPACEC", 652 $line, length($m)+1, $file, $l, 653 "missing space before colon"); 654 } 655 } 656 # check spaces after colon 657 if($nostr =~ /^(.*[^'"]\?[^'"].*)\:(\w|\)|\]|')/i) { 658 my $m = $1; 659 my $e = $nostr; 660 $e =~ s/'(.)':'(.)'/$1:$2/g; # eliminate chars quotes that surround colon 661 $e =~ s/':'//g; # ignore these 662 if($e =~ /^(.*[^'"]\?[^'"].*)\:(\w|\)|\]|')/i) { 663 checkwarn("NOSPACEC", 664 $line, length($m)+1, $file, $l, 665 "missing space after colon"); 666 } 667 } 668 669 # check spaces before question mark 670 if($nostr =~ /^(.*)(\w|\)|\]|')\?/i) { 671 my $m = $1; 672 my $e = $nostr; 673 $e =~ s/'?'//g; # ignore these 674 if($e =~ /^(.*)(\w|\)|\]|')\?/i) { 675 checkwarn("NOSPACEQ", 676 $line, length($m)+1, $file, $l, 677 "missing space before question mark"); 678 } 679 } 680 # check spaces after question mark 681 if($nostr =~ /^(.*)\?\w/i) { 682 checkwarn("NOSPACEQ", 683 $line, length($1)+1, $file, $l, 684 "missing space after question mark"); 685 } 686 687 # check spaces before less or greater than 688 if($nostr =~ /^(.*)(\w|\)|\])[<>]/) { 689 checkwarn("NOSPACETHAN", 690 $line, length($1)+1, $file, $l, 691 "missing space before less or greater than"); 692 } 693 # check spaces after less or greater than 694 if($nostr =~ /^(.*)[^-][<>](\w|\(|\[)/) { 695 checkwarn("NOSPACETHAN", 696 $line, length($1)+1, $file, $l, 697 "missing space after less or greater than"); 698 } 699 700 # check spaces before close parentheses, unless it was a space or a 701 # close parenthesis! 702 if($l =~ /(.*[^\) ]) \)/) { 703 checkwarn("SPACEBEFORECLOSE", 704 $line, length($1)+1, $file, $l, 705 "space before close parenthesis"); 706 } 707 708 # check spaces before comma! 709 if($l =~ /(.*[^ ]) ,/) { 710 checkwarn("SPACEBEFORECOMMA", 711 $line, length($1)+1, $file, $l, 712 "space before comma"); 713 } 714 715 # check for "return(" without space 716 if($l =~ /^(.*)return\(/) { 717 if($1 =~ / *\#/) { 718 # this is a #if, treat it differently 719 } 720 else { 721 checkwarn("RETURNNOSPACE", $line, length($1)+6, $file, $l, 722 "return without space before paren"); 723 } 724 } 725 726 # check for "sizeof" without parenthesis 727 if(($l =~ /^(.*)sizeof *([ (])/) && ($2 ne "(")) { 728 if($1 =~ / *\#/) { 729 # this is a #if, treat it differently 730 } 731 else { 732 checkwarn("SIZEOFNOPAREN", $line, length($1)+6, $file, $l, 733 "sizeof without parenthesis"); 734 } 735 } 736 737 # check for comma without space 738 if($l =~ /^(.*),[^ \n]/) { 739 my $pref=$1; 740 my $ign=0; 741 if($pref =~ / *\#/) { 742 # this is a #if, treat it differently 743 $ign=1; 744 } 745 elsif($pref =~ /\/\*/) { 746 # this is a comment 747 $ign=1; 748 } 749 elsif($pref =~ /[\"\']/) { 750 $ign = 1; 751 # There is a quote here, figure out whether the comma is 752 # within a string or '' or not. 753 if($pref =~ /\"/) { 754 # within a string 755 } 756 elsif($pref =~ /\'$/) { 757 # a single letter 758 } 759 else { 760 $ign = 0; 761 } 762 } 763 if(!$ign) { 764 checkwarn("COMMANOSPACE", $line, length($pref)+1, $file, $l, 765 "comma without following space"); 766 } 767 } 768 769 # check for "} else" 770 if($l =~ /^(.*)\} *else/) { 771 checkwarn("BRACEELSE", 772 $line, length($1), $file, $l, "else after closing brace on same line"); 773 } 774 # check for "){" 775 if($l =~ /^(.*)\)\{/) { 776 checkwarn("PARENBRACE", 777 $line, length($1)+1, $file, $l, "missing space after close paren"); 778 } 779 # check for "^{" with an empty line before it 780 if(($l =~ /^\{/) && ($prevl =~ /^[ \t]*\z/)) { 781 checkwarn("EMPTYLINEBRACE", 782 $line, 0, $file, $l, "empty line before open brace"); 783 } 784 785 # check for space before the semicolon last in a line 786 if($l =~ /^(.*[^ ].*) ;$/) { 787 checkwarn("SPACESEMICOLON", 788 $line, length($1), $file, $ol, "no space before semicolon"); 789 } 790 791 # check for space before the colon in a switch label 792 if($l =~ /^( *(case .+|default)) :/) { 793 checkwarn("SPACESWITCHCOLON", 794 $line, length($1), $file, $ol, "no space before colon of switch label"); 795 } 796 797 if($prevl !~ /\?\z/ && $l =~ /^ +([A-Za-z_][A-Za-z0-9_]*):$/ && $1 ne 'default') { 798 checkwarn("SPACEBEFORELABEL", 799 $line, length($1), $file, $ol, "no space before label"); 800 } 801 802 # scan for use of banned functions 803 if($l =~ /^(.*\W) 804 (gmtime|localtime| 805 gets| 806 strtok| 807 v?sprintf| 808 (str|_mbs|_tcs|_wcs)n?cat| 809 LoadLibrary(Ex)?(A|W)?| 810 _?w?access) 811 \s*\( 812 /x) { 813 checkwarn("BANNEDFUNC", 814 $line, length($1), $file, $ol, 815 "use of $2 is banned"); 816 } 817 if($warnings{"STRERROR"}) { 818 # scan for use of banned strerror. This is not a BANNEDFUNC to 819 # allow for individual enable/disable of this warning. 820 if($l =~ /^(.*\W)(strerror)\s*\(/x) { 821 if($1 !~ /^ *\#/) { 822 # skip preprocessor lines 823 checkwarn("STRERROR", 824 $line, length($1), $file, $ol, 825 "use of $2 is banned"); 826 } 827 } 828 } 829 if($warnings{"STRNCPY"}) { 830 # scan for use of banned strncpy. This is not a BANNEDFUNC to 831 # allow for individual enable/disable of this warning. 832 if($l =~ /^(.*\W)(strncpy)\s*\(/x) { 833 if($1 !~ /^ *\#/) { 834 # skip preprocessor lines 835 checkwarn("STRNCPY", 836 $line, length($1), $file, $ol, 837 "use of $2 is banned"); 838 } 839 } 840 } 841 if($warnings{"STDERR"}) { 842 # scan for use of banned stderr. This is not a BANNEDFUNC to 843 # allow for individual enable/disable of this warning. 844 if($l =~ /^([^\"-]*\W)(stderr)[^\"_]/x) { 845 if($1 !~ /^ *\#/) { 846 # skip preprocessor lines 847 checkwarn("STDERR", 848 $line, length($1), $file, $ol, 849 "use of $2 is banned (use tool_stderr instead)"); 850 } 851 } 852 } 853 # scan for use of snprintf for curl-internals reasons 854 if($l =~ /^(.*\W)(v?snprintf)\s*\(/x) { 855 checkwarn("SNPRINTF", 856 $line, length($1), $file, $ol, 857 "use of $2 is banned"); 858 } 859 860 # scan for use of non-binary fopen without the macro 861 if($l =~ /^(.*\W)fopen\s*\([^,]*, *\"([^"]*)/) { 862 my $mode = $2; 863 if($mode !~ /b/) { 864 checkwarn("FOPENMODE", 865 $line, length($1), $file, $ol, 866 "use of non-binary fopen without FOPEN_* macro: $mode"); 867 } 868 } 869 870 # check for open brace first on line but not first column only alert 871 # if previous line ended with a close paren and it wasn't a cpp line 872 if(($prevl =~ /\)\z/) && ($l =~ /^( +)\{/) && !$prevp) { 873 checkwarn("BRACEPOS", 874 $line, length($1), $file, $ol, "badly placed open brace"); 875 } 876 877 # if the previous line starts with if/while/for AND ends with an open 878 # brace, or an else statement, check that this line is indented $indent 879 # more steps, if not a cpp line 880 if(!$prevp && ($prevl =~ /^( *)((if|while|for)\(.*\{|else)\z/)) { 881 my $first = length($1); 882 # this line has some character besides spaces 883 if($l =~ /^( *)[^ ]/) { 884 my $second = length($1); 885 my $expect = $first+$indent; 886 if($expect != $second) { 887 my $diff = $second - $first; 888 checkwarn("INDENTATION", $line, length($1), $file, $ol, 889 "not indented $indent steps (uses $diff)"); 890 891 } 892 } 893 } 894 895 # if the previous line starts with if/while/for AND ends with a closed 896 # parenthesis and there's an equal number of open and closed 897 # parentheses, check that this line is indented $indent more steps, if 898 # not a cpp line 899 elsif(!$prevp && ($prevl =~ /^( *)(if|while|for)(\(.*\))\z/)) { 900 my $first = length($1); 901 my $op = $3; 902 my $cl = $3; 903 904 $op =~ s/[^(]//g; 905 $cl =~ s/[^)]//g; 906 907 if(length($op) == length($cl)) { 908 # this line has some character besides spaces 909 if($l =~ /^( *)[^ ]/) { 910 my $second = length($1); 911 my $expect = $first+$indent; 912 if($expect != $second) { 913 my $diff = $second - $first; 914 checkwarn("INDENTATION", $line, length($1), $file, $ol, 915 "not indented $indent steps (uses $diff)"); 916 } 917 } 918 } 919 } 920 921 # check for 'char * name' 922 if(($l =~ /(^.*(char|int|long|void|CURL|CURLM|CURLMsg|[cC]url_[A-Za-z_]+|struct [a-zA-Z_]+) *(\*+)) (\w+)/) && ($4 !~ /^(const|volatile)$/)) { 923 checkwarn("ASTERISKSPACE", 924 $line, length($1), $file, $ol, 925 "space after declarative asterisk"); 926 } 927 # check for 'char*' 928 if(($l =~ /(^.*(char|int|long|void|curl_slist|CURL|CURLM|CURLMsg|curl_httppost|sockaddr_in|FILE)\*)/)) { 929 checkwarn("ASTERISKNOSPACE", 930 $line, length($1)-1, $file, $ol, 931 "no space before asterisk"); 932 } 933 934 # check for 'void func() {', but avoid false positives by requiring 935 # both an open and closed parentheses before the open brace 936 if($l =~ /^((\w).*)\{\z/) { 937 my $k = $1; 938 $k =~ s/const *//; 939 $k =~ s/static *//; 940 if($k =~ /\(.*\)/) { 941 checkwarn("BRACEPOS", 942 $line, length($l)-1, $file, $ol, 943 "wrongly placed open brace"); 944 } 945 } 946 947 # check for equals sign without spaces next to it 948 if($nostr =~ /(.*)\=[a-z0-9]/i) { 949 checkwarn("EQUALSNOSPACE", 950 $line, length($1)+1, $file, $ol, 951 "no space after equals sign"); 952 } 953 # check for equals sign without spaces before it 954 elsif($nostr =~ /(.*)[a-z0-9]\=/i) { 955 checkwarn("NOSPACEEQUALS", 956 $line, length($1)+1, $file, $ol, 957 "no space before equals sign"); 958 } 959 960 # check for plus signs without spaces next to it 961 if($nostr =~ /(.*)[^+]\+[a-z0-9]/i) { 962 checkwarn("PLUSNOSPACE", 963 $line, length($1)+1, $file, $ol, 964 "no space after plus sign"); 965 } 966 # check for plus sign without spaces before it 967 elsif($nostr =~ /(.*)[a-z0-9]\+[^+]/i) { 968 checkwarn("NOSPACEPLUS", 969 $line, length($1)+1, $file, $ol, 970 "no space before plus sign"); 971 } 972 973 # check for semicolons without space next to it 974 if($nostr =~ /(.*)\;[a-z0-9]/i) { 975 checkwarn("SEMINOSPACE", 976 $line, length($1)+1, $file, $ol, 977 "no space after semicolon"); 978 } 979 980 # typedef struct ... { 981 if($nostr =~ /^(.*)typedef struct.*{/) { 982 checkwarn("TYPEDEFSTRUCT", 983 $line, length($1)+1, $file, $ol, 984 "typedef'ed struct"); 985 } 986 987 if($nostr =~ /(.*)! +(\w|\()/) { 988 checkwarn("EXCLAMATIONSPACE", 989 $line, length($1)+1, $file, $ol, 990 "space after exclamation mark"); 991 } 992 993 # check for more than one consecutive space before open brace or 994 # question mark. Skip lines containing strings since they make it hard 995 # due to artificially getting multiple spaces 996 if(($l eq $nostr) && 997 $nostr =~ /^(.*(\S)) + [{?]/i) { 998 checkwarn("MULTISPACE", 999 $line, length($1)+1, $file, $ol, 1000 "multiple spaces"); 1001 } 1002 preproc: 1003 if($prep) { 1004 # scan for use of banned symbols on a preprocessor line 1005 if($l =~ /^(^|.*\W) 1006 (WIN32) 1007 (\W|$) 1008 /x) { 1009 checkwarn("BANNEDPREPROC", 1010 $line, length($1), $file, $ol, 1011 "use of $2 is banned from preprocessor lines" . 1012 (($2 eq "WIN32") ? ", use _WIN32 instead" : "")); 1013 } 1014 } 1015 $line++; 1016 $prevp = $prep; 1017 $prevl = $ol if(!$prep); 1018 $prevpl = $ol if($prep); 1019 } 1020 1021 if(!scalar(@copyright)) { 1022 checkwarn("COPYRIGHT", 1, 0, $file, "", "Missing copyright statement", 1); 1023 } 1024 1025 # COPYRIGHTYEAR is an extended warning so we must first see if it has been 1026 # enabled in .checksrc 1027 if(defined($warnings{"COPYRIGHTYEAR"})) { 1028 # The check for updated copyrightyear is overly complicated in order to 1029 # not punish current hacking for past sins. The copyright years are 1030 # right now a bit behind, so enforcing copyright year checking on all 1031 # files would cause hundreds of errors. Instead we only look at files 1032 # which are tracked in the Git repo and edited in the workdir, or 1033 # committed locally on the branch without being in upstream master. 1034 # 1035 # The simple and naive test is to simply check for the current year, 1036 # but updating the year even without an edit is against project policy 1037 # (and it would fail every file on January 1st). 1038 # 1039 # A rather more interesting, and correct, check would be to not test 1040 # only locally committed files but inspect all files wrt the year of 1041 # their last commit. Removing the `git rev-list origin/master..HEAD` 1042 # condition below will enforce copyright year checks against the year 1043 # the file was last committed (and thus edited to some degree). 1044 my $commityear = undef; 1045 @copyright = sort {$$b{year} cmp $$a{year}} @copyright; 1046 1047 # if the file is modified, assume commit year this year 1048 if(`git status -s -- "$file"` =~ /^ [MARCU]/) { 1049 $commityear = (localtime(time))[5] + 1900; 1050 } 1051 else { 1052 # min-parents=1 to ignore wrong initial commit in truncated repos 1053 my $grl = `git rev-list --max-count=1 --min-parents=1 --timestamp HEAD -- "$file"`; 1054 if($grl) { 1055 chomp $grl; 1056 $commityear = (localtime((split(/ /, $grl))[0]))[5] + 1900; 1057 } 1058 } 1059 1060 if(defined($commityear) && scalar(@copyright) && 1061 $copyright[0]{year} != $commityear) { 1062 checkwarn("COPYRIGHTYEAR", $copyright[0]{line}, $copyright[0]{col}, 1063 $file, $copyright[0]{code}, 1064 "Copyright year out of date, should be $commityear, " . 1065 "is $copyright[0]{year}", 1); 1066 } 1067 } 1068 1069 if($incomment) { 1070 checkwarn("OPENCOMMENT", 1, 0, $file, "", "Missing closing comment", 1); 1071 } 1072 1073 checksrc_endoffile($file); 1074 1075 close($R); 1076 1077} 1078 1079 1080if($errors || $warnings || $verbose) { 1081 printf "checksrc: %d errors and %d warnings\n", $errors, $warnings; 1082 if($suppressed) { 1083 printf "checksrc: %d errors and %d warnings suppressed\n", 1084 $serrors, 1085 $swarnings; 1086 } 1087 exit 5; # return failure 1088} 1089