quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

checksrc.pl (39380B)


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