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