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