• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/perl
2#***************************************************************************
3#                                  _   _ ____  _
4#  Project                     ___| | | |  _ \| |
5#                             / __| | | | |_) | |
6#                            | (__| |_| |  _ <| |___
7#                             \___|\___/|_| \_\_____|
8#
9# Copyright (C) 2011 - 2016, 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
24my $max_column = 79;
25my $indent = 2;
26
27my $warnings;
28my $errors;
29my $supressed; # whitelisted problems
30my $file;
31my $dir=".";
32my $wlist;
33my $windows_os = $^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin';
34my $verbose;
35my %whitelist;
36
37my %warnings = (
38    'LONGLINE' =>         "Line longer than $max_column",
39    'TABS' =>             'TAB characters not allowed',
40    'TRAILINGSPACE' =>    'Trailing white space on the line',
41    'CPPCOMMENTS' =>      '// comment detected',
42    'SPACEBEFOREPAREN' => 'space before an open parenthesis',
43    'SPACEAFTERPAREN'  => 'space after open parenthesis',
44    'SPACEBEFORECLOSE' => 'space before a close parenthesis',
45    'SPACEBEFORECOMMA' => 'space before a comma',
46    'RETURNNOSPACE'    => 'return without space',
47    'COMMANOSPACE'     => 'comma without following space',
48    'BRACEELSE'        => '} else on the same line',
49    'PARENBRACE'       => '){ without sufficient space',
50    'SPACESEMILCOLON'  => 'space before semicolon',
51    'BANNEDFUNC'       => 'a banned function was used',
52    'FOPENMODE'        => 'fopen needs a macro for the mode string',
53    'BRACEPOS'         => 'wrong position for an open brace',
54    'INDENTATION'      => 'wrong start column for code',
55    'COPYRIGHT'        => 'file missing a copyright statement',
56    'BADCOMMAND'       => 'bad !checksrc! instruction',
57    'UNUSEDIGNORE'     => 'a warning ignore was not used',
58    'OPENCOMMENT'      => 'file ended with a /* comment still "open"'
59    );
60
61sub readwhitelist {
62    open(W, "<$dir/checksrc.whitelist");
63    my @all=<W>;
64    for(@all) {
65        $windows_os ? $_ =~ s/\r?\n$// : chomp;
66        $whitelist{$_}=1;
67    }
68    close(W);
69}
70
71sub checkwarn {
72    my ($name, $num, $col, $file, $line, $msg, $error) = @_;
73
74    my $w=$error?"error":"warning";
75    my $nowarn=0;
76
77    #if(!$warnings{$name}) {
78    #    print STDERR "Dev! there's no description for $name!\n";
79    #}
80
81    # checksrc.whitelist
82    if($whitelist{$line}) {
83        $nowarn = 1;
84    }
85    # !checksrc! controlled
86    elsif($ignore{$name}) {
87        $ignore{$name}--;
88        $ignore_used{$name}++;
89        $nowarn = 1;
90        if(!$ignore{$name}) {
91            # reached zero, enable again
92            enable_warn($name, $line, $file, $l);
93        }
94    }
95
96    if($nowarn) {
97        $supressed++;
98        if($w) {
99            $swarnings++;
100        }
101        else {
102            $serrors++;
103        }
104        return;
105    }
106
107    if($w) {
108        $warnings++;
109    }
110    else {
111        $errors++;
112    }
113
114    $col++;
115    print "$file:$num:$col: $w: $msg ($name)\n";
116    print " $line\n";
117
118    if($col < 80) {
119        my $pref = (' ' x $col);
120        print "${pref}^\n";
121    }
122}
123
124$file = shift @ARGV;
125
126while(1) {
127
128    if($file =~ /-D(.*)/) {
129        $dir = $1;
130        $file = shift @ARGV;
131        next;
132    }
133    elsif($file =~ /-W(.*)/) {
134        $wlist .= " $1 ";
135        $file = shift @ARGV;
136        next;
137    }
138    elsif($file =~ /^(-h|--help)/) {
139        undef $file;
140        last;
141    }
142
143    last;
144}
145
146if(!$file) {
147    print "checksrc.pl [option] <file1> [file2] ...\n";
148    print " Options:\n";
149    print "  -D[DIR]   Directory to prepend file names\n";
150    print "  -h        Show help output\n";
151    print "  -W[file]  Whitelist the given file - ignore all its flaws\n";
152    print "\nDetects and warns for these problems:\n";
153    for(sort keys %warnings) {
154        printf (" %-18s: %s\n", $_, $warnings{$_});
155    }
156    exit;
157}
158
159readwhitelist();
160
161do {
162    if("$wlist" !~ / $file /) {
163        my $fullname = $file;
164        $fullname = "$dir/$file" if ($fullname !~ '^\.?\.?/');
165        scanfile($fullname);
166    }
167    $file = shift @ARGV;
168
169} while($file);
170
171sub checksrc_clear {
172    undef %ignore;
173    undef %ignore_set;
174    undef @ignore_line;
175}
176
177sub checksrc_endoffile {
178    my ($file) = @_;
179    for(keys %ignore_set) {
180        if($ignore_set{$_} && !$ignore_used{$_}) {
181            checkwarn("UNUSEDIGNORE", $ignore_set{$_},
182                      length($_)+11, $file,
183                      $ignore_line[$ignore_set{$_}],
184                      "Unused ignore: $_");
185        }
186    }
187}
188
189sub enable_warn {
190    my ($what, $line, $file, $l) = @_;
191
192    # switch it back on, but warn if not triggered!
193    if(!$ignore_used{$what}) {
194        checkwarn("UNUSEDIGNORE",
195                  $line, length($what) + 11, $file, $l,
196                  "No warning was inhibited!");
197    }
198    $ignore_set{$what}=0;
199    $ignore_used{$what}=0;
200    $ignore{$what}=0;
201}
202sub checksrc {
203    my ($cmd, $line, $file, $l) = @_;
204    if($cmd =~ / *([^ ]*) *(.*)/) {
205        my ($enable, $what) = ($1, $2);
206        $what =~ s: *\*/$::; # cut off end of C comment
207        # print "ENABLE $enable WHAT $what\n";
208        if($enable eq "disable") {
209            my ($warn, $scope)=($1, $2);
210            if($what =~ /([^ ]*) +(.*)/) {
211                ($warn, $scope)=($1, $2);
212            }
213            else {
214                $warn = $what;
215                $scope = 1;
216            }
217            # print "IGNORE $warn for SCOPE $scope\n";
218            if($scope eq "all") {
219                $scope=999999;
220            }
221
222            if($ignore_set{$warn}) {
223                checkwarn("BADCOMMAND",
224                          $line, 0, $file, $l,
225                          "$warn already disabled from line $ignore_set{$warn}");
226            }
227            else {
228                $ignore{$warn}=$scope;
229                $ignore_set{$warn}=$line;
230                $ignore_line[$line]=$l;
231            }
232        }
233        elsif($enable eq "enable") {
234            enable_warn($what, $line, $file, $l);
235        }
236        else {
237            checkwarn("BADCOMMAND",
238                      $line, 0, $file, $l,
239                      "Illegal !checksrc! command");
240        }
241    }
242}
243
244sub scanfile {
245    my ($file) = @_;
246
247    my $line = 1;
248    my $prevl;
249    my $l;
250    open(R, "<$file") || die "failed to open $file";
251
252    my $incomment=0;
253    my $copyright=0;
254    checksrc_clear(); # for file based ignores
255
256    while(<R>) {
257        $windows_os ? $_ =~ s/\r?\n$// : chomp;
258        my $l = $_;
259        my $ol = $l; # keep the unmodified line for error reporting
260        my $column = 0;
261
262        # check for !checksrc! commands
263        if($l =~ /\!checksrc\! (.*)/) {
264            my $cmd = $1;
265            checksrc($cmd, $line, $file, $l)
266        }
267
268        # check for a copyright statement
269        if(!$copyright && ($l =~ /copyright .* \d\d\d\d/i)) {
270            $copyright=1;
271        }
272
273        # detect long lines
274        if(length($l) > $max_column) {
275            checkwarn("LONGLINE", $line, length($l), $file, $l,
276                      "Longer than $max_column columns");
277        }
278        # detect TAB characters
279        if($l =~ /^(.*)\t/) {
280            checkwarn("TABS",
281                      $line, length($1), $file, $l, "Contains TAB character", 1);
282        }
283        # detect trailing white space
284        if($l =~ /^(.*)[ \t]+\z/) {
285            checkwarn("TRAILINGSPACE",
286                      $line, length($1), $file, $l, "Trailing whitespace");
287        }
288
289        # ------------------------------------------------------------
290        # Above this marker, the checks were done on lines *including*
291        # comments
292        # ------------------------------------------------------------
293
294        # strip off C89 comments
295
296      comment:
297        if(!$incomment) {
298            if($l =~ s/\/\*.*\*\// /g) {
299                # full /* comments */ were removed!
300            }
301            if($l =~ s/\/\*.*//) {
302                # start of /* comment was removed
303                $incomment = 1;
304            }
305        }
306        else {
307            if($l =~ s/.*\*\///) {
308                # end of comment */ was removed
309                $incomment = 0;
310                goto comment;
311            }
312            else {
313                # still within a comment
314                $l="";
315            }
316        }
317
318        # ------------------------------------------------------------
319        # Below this marker, the checks were done on lines *without*
320        # comments
321        # ------------------------------------------------------------
322
323        # crude attempt to detect // comments without too many false
324        # positives
325        if($l =~ /^([^"\*]*)[^:"]\/\//) {
326            checkwarn("CPPCOMMENTS",
327                      $line, length($1), $file, $l, "\/\/ comment");
328        }
329
330        # check spaces after for/if/while
331        if($l =~ /^(.*)(for|if|while) \(/) {
332            if($1 =~ / *\#/) {
333                # this is a #if, treat it differently
334            }
335            else {
336                checkwarn("SPACEBEFOREPAREN", $line, length($1)+length($2), $file, $l,
337                          "$2 with space");
338            }
339        }
340
341        # check spaces after open parentheses
342        if($l =~ /^(.*[a-z])\( /i) {
343            checkwarn("SPACEAFTERPAREN",
344                      $line, length($1)+1, $file, $l,
345                      "space after open parenthesis");
346        }
347
348        # check spaces before close parentheses, unless it was a space or a
349        # close parenthesis!
350        if($l =~ /(.*[^\) ]) \)/) {
351            checkwarn("SPACEBEFORECLOSE",
352                      $line, length($1)+1, $file, $l,
353                      "space before close parenthesis");
354        }
355
356        # check spaces before comma!
357        if($l =~ /(.*[^ ]) ,/) {
358            checkwarn("SPACEBEFORECOMMA",
359                      $line, length($1)+1, $file, $l,
360                      "space before comma");
361        }
362
363        # check for "return(" without space
364        if($l =~ /^(.*)return\(/) {
365            if($1 =~ / *\#/) {
366                # this is a #if, treat it differently
367            }
368            else {
369                checkwarn("RETURNNOSPACE", $line, length($1)+6, $file, $l,
370                          "return without space before paren");
371            }
372        }
373
374        # check for comma without space
375        if($l =~ /^(.*),[^ \n]/) {
376            my $pref=$1;
377            my $ign=0;
378            if($pref =~ / *\#/) {
379                # this is a #if, treat it differently
380                $ign=1;
381            }
382            elsif($pref =~ /\/\*/) {
383                # this is a comment
384                $ign=1;
385            }
386            elsif($pref =~ /[\"\']/) {
387                $ign = 1;
388                # There is a quote here, figure out whether the comma is
389                # within a string or '' or not.
390                if($pref =~ /\"/) {
391                    # withing a string
392                }
393                elsif($pref =~ /\'$/) {
394                    # a single letter
395                }
396                else {
397                    $ign = 0;
398                }
399            }
400            if(!$ign) {
401                checkwarn("COMMANOSPACE", $line, length($pref)+1, $file, $l,
402                          "comma without following space");
403            }
404        }
405
406        # check for "} else"
407        if($l =~ /^(.*)\} *else/) {
408            checkwarn("BRACEELSE",
409                      $line, length($1), $file, $l, "else after closing brace on same line");
410        }
411        # check for "){"
412        if($l =~ /^(.*)\)\{/) {
413            checkwarn("PARENBRACE",
414                      $line, length($1)+1, $file, $l, "missing space after close paren");
415        }
416
417        # check for space before the semicolon last in a line
418        if($l =~ /^(.*[^ ].*) ;$/) {
419            checkwarn("SPACESEMILCOLON",
420                      $line, length($1), $file, $ol, "space before last semicolon");
421        }
422
423        # scan for use of banned functions
424        if($l =~ /^(.*\W)
425                   (gets|
426                    v?sprintf|
427                    (str|_mbs|_tcs|_wcs)n?cat|
428                    LoadLibrary(Ex)?(A|W)?)
429                   \s*\(
430                 /x) {
431            checkwarn("BANNEDFUNC",
432                      $line, length($1), $file, $ol,
433                      "use of $2 is banned");
434        }
435
436        # scan for use of non-binary fopen without the macro
437        if($l =~ /^(.*\W)fopen\s*\([^,]*, *\"([^"]*)/) {
438            my $mode = $2;
439            if($mode !~ /b/) {
440                checkwarn("FOPENMODE",
441                          $line, length($1), $file, $ol,
442                          "use of non-binary fopen without FOPEN_* macro: $mode");
443            }
444        }
445
446        # check for open brace first on line but not first column
447        # only alert if previous line ended with a close paren and wasn't a cpp
448        # line
449        if((($prevl =~ /\)\z/) && ($prevl !~ /^ *#/)) && ($l =~ /^( +)\{/)) {
450            checkwarn("BRACEPOS",
451                      $line, length($1), $file, $ol, "badly placed open brace");
452        }
453
454        # if the previous line starts with if/while/for AND ends with an open
455        # brace, check that this line is indented $indent more steps, if not
456        # a cpp line
457        if($prevl =~ /^( *)(if|while|for)\(.*\{\z/) {
458            my $first = length($1);
459
460            # this line has some character besides spaces
461            if(($l !~ /^ *#/) && ($l =~ /^( *)[^ ]/)) {
462                my $second = length($1);
463                my $expect = $first+$indent;
464                if($expect != $second) {
465                    my $diff = $second - $first;
466                    checkwarn("INDENTATION", $line, length($1), $file, $ol,
467                              "not indented $indent steps, uses $diff)");
468
469                }
470            }
471        }
472
473        $line++;
474        $prevl = $ol;
475    }
476
477    if(!$copyright) {
478        checkwarn("COPYRIGHT", 1, 0, $file, "", "Missing copyright statement", 1);
479    }
480    if($incomment) {
481        checkwarn("OPENCOMMENT", 1, 0, $file, "", "Missing closing comment", 1);
482    }
483
484    checksrc_endoffile($file);
485
486    close(R);
487
488}
489
490
491if($errors || $warnings || $verbose) {
492    printf "checksrc: %d errors and %d warnings\n", $errors, $warnings;
493    if($supressed) {
494        printf "checksrc: %d errors and %d warnings suppressed\n",
495        $serrors,
496        $swarnings;
497    }
498    exit 5; # return failure
499}
500