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