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