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