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