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