cvsimport
[findutils.git] / build-aux / src-sniff.py
bloba46023c9212c96eb0ff9f064ee599281b02ac0b9
1 #! /usr/bin/env python
4 import re
5 import sys
7 C_ISH_FILENAME = "\.(c|cc|h|cpp|cxx|hxx)$"
8 C_ISH_FILENAME_RE = re.compile(C_ISH_FILENAME)
9 C_MODULE_FILENAME_RE = re.compile("\.(c|cc|cpp|cxx)$")
10 FIRST_INCLUDE = 'config.h'
11 problems = 0
14 def Problem(**kwargs):
15 global problems
16 problems += 1
17 msg = kwargs['message']
18 if kwargs['line']:
19 location = "%(filename)s:%(line)d" % kwargs
20 else:
21 location = "%(filename)s" % kwargs
22 detail = msg % kwargs
23 print >>sys.stderr, "error: %s: %s" % (location, detail)
26 class RegexSniffer(object):
27 def __init__(self, source, message):
28 super(RegexSniffer, self).__init__()
29 self._regex = re.compile(source)
30 self._msg = message
31 def Sniff(self, text, filename, line):
32 m = self._regex.search(text)
33 if m:
34 if line is None:
35 line = 1 + m.string.count('\n', 1, m.start(0))
36 args = {
37 'filename' : filename,
38 'line' : line,
39 'fulltext' : text,
40 'matchtext': m.group(0),
41 'message' : self._msg
43 Problem(**args)
46 class RegexChecker(object):
47 def __init__(self, regex, line_smells, file_smells):
48 super(RegexChecker, self).__init__(self)
49 self._regex = re.compile(regex)
50 self._line_sniffers = [RegexSniffer(s[0],s[1]) for s in line_smells]
51 self._file_sniffers = [RegexSniffer(s[0],s[1]) for s in file_smells]
52 def Check(self, filename, lines, fulltext):
53 if self._regex.search(filename):
54 # We recognise this type of file.
55 for line_number, line_text in lines:
56 for sniffer in self._line_sniffers:
57 sniffer.Sniff(line_text, filename, line_number)
58 for sniffer in self._file_sniffers:
59 sniffer.Sniff(fulltext, filename, None)
60 else:
61 # We don't know how to check this file. Skip it.
62 pass
65 checkers = [
66 # Check C-like languages for C code smells.
67 RegexChecker(C_ISH_FILENAME_RE,
68 # line smells
70 [r'(?<!\w)free \(\(', "don't cast the argument to free()"],
71 [r'\*\) *x(m|c|re)alloc(?!\w)',"don't cast the result of x*alloc"],
72 [r'\*\) *alloca(?!\w)',"don't cast the result of alloca"],
73 [r'[ ] ',"found SPACE-TAB; remove the space"],
74 [r'(?<!\w)([fs]?scanf|ato([filq]|ll))(?!\w)',
75 'do not use *scan''f, ato''f, ato''i, ato''l, ato''ll, ato''q, or ss''canf'],
76 [r'error \(EXIT_SUCCESS',"passing EXIT_SUCCESS to error is confusing"],
77 [r'file[s]ystem', "prefer writing 'file system' to 'filesystem'"],
78 [r'HAVE''_CONFIG_H', "Avoid checking HAVE_CONFIG_H"],
79 # [r'HAVE_FCNTL_H', "Avoid checking HAVE_FCNTL_H"],
80 [r'O_NDELAY', "Avoid using O_NDELAY"],
81 [r'the *the', "'the the' is probably not deliberate"],
82 [r'(?<!\w)error \([^_"]*[^_]"[^"]*[a-z]{3}', "untranslated error message"],
83 [r'^# *if\s+defined *\(', "useless parentheses in '#if defined'"],
87 [r'# *include <assert.h>(?!.*assert \()',
88 "If you include <assert.h>, use assert()."],
89 [r'# *include "quotearg.h"(?!.*(?<!\w)quotearg(_[^ ]+)? \()',
90 "If you include \"quotearg.h\", use one of its functions."],
91 [r'# *include "quote.h"(?!.*(?<!\w)quote(_[^ ]+)? \()',
92 "If you include \"quote.h\", use one of its functions."],
93 ]),
94 # Check Makefiles for Makefile code smells.
95 RegexChecker('(^|/)[Mm]akefile(.am|.in)?',
96 [ [r'^ ', "Spaces at start of line"], ],
97 []),
98 # Check everything for whitespace problems.
99 # RegexChecker('', [], [[r'\s$', "trailing whitespace"],]),
100 # Check everything for out of date addresses.
101 RegexChecker('', [], [
102 [r'675\s*Mass\s*Ave,\s*02139[^a-zA-Z]*USA',
103 "out of date FSF address"],
104 [r'59 Temple Place.*02111-?1307\s*USA',
105 "out of date FSF address"],
107 # Bourne shell code smells
108 RegexChecker('\.sh$',
110 ['for\s*\w+\s*in.*;\s*do',
111 # Solaris 10 /bin/sh rejects this, see Autoconf manual
112 "for loops should not contain a 'do' on the same line."],
113 ], []),
117 # missing check: ChangeLog prefixes
118 # missing: sc_always_defined_macros from coreutils
119 # missing: sc_tight_scope
122 def Warning(filename, desc):
123 print >> sys.stderr, "warning: %s: %s" % (filename, desc)
126 def BuildIncludeList(text):
127 """Build a list of included files, with line numbers.
128 Args:
129 text: the full text of the source file
130 Returns:
131 [ ('config.h',32), ('assert.h',33), ... ]
133 include_re = re.compile(r'# *include +[<"](.*)[>"]')
134 includes = []
135 last_include_pos = 1
136 line = 1
137 for m in include_re.finditer(text):
138 header = m.group(1)
139 # Count only the number of lines between the last include and
140 # this one. Counting them from the beginning would be quadratic.
141 line += m.string.count('\n', last_include_pos, m.start(0))
142 last_include_pos = m.end()
143 includes.append( (header,line) )
144 return includes
147 def CheckStatHeader(filename, lines, fulltext):
148 stat_hdr_re = re.compile(r'# *include .*<sys/stat.h>')
149 # It's OK to have a pointer though.
150 stat_use_re = re.compile(r'struct stat\W *[^*]')
151 for line in lines:
152 m = stat_use_re.search(line[1])
153 if m:
154 msg = "If you use struct stat, you must #include <sys/stat.h> first"
155 Problem(filename = filename, line = line[0], message = msg)
156 # Diagnose only once
157 break
158 m = stat_hdr_re.search(line[1])
159 if m:
160 break
162 def CheckFirstInclude(filename, lines, fulltext):
163 includes = BuildIncludeList(fulltext)
164 #print "Include map:"
165 #for name, line in includes:
166 # print "%s:%d: %s" % (filename, line, name)
167 if includes:
168 actual_first_include = includes[0][0]
169 else:
170 actual_first_include = None
171 if actual_first_include and actual_first_include != FIRST_INCLUDE:
172 if FIRST_INCLUDE in [inc[0] for inc in includes]:
173 msg = ("%(actual_first_include)s is the first included file, "
174 "but %(required_first_include)s should be included first")
175 Problem(filename=filename, line=includes[0][1], message=msg,
176 actual_first_include=actual_first_include,
177 required_first_include = FIRST_INCLUDE)
178 if FIRST_INCLUDE not in [inc[0] for inc in includes]:
179 Warning(filename,
180 "%s should be included by most files" % FIRST_INCLUDE)
183 def SniffSourceFile(filename, lines, fulltext):
184 if C_MODULE_FILENAME_RE.search(filename):
185 CheckFirstInclude(filename, lines, fulltext)
186 CheckStatHeader (filename, lines, fulltext)
187 for checker in checkers:
188 checker.Check(filename, lines, fulltext)
191 def main(args):
192 "main program"
193 for srcfile in args[1:]:
194 f = open(srcfile)
195 line_number = 1
196 lines = []
197 for line in f.readlines():
198 lines.append( (line_number, line) )
199 line_number += 1
200 fulltext = ''.join([line[1] for line in lines])
201 SniffSourceFile(srcfile, lines, fulltext)
202 f.close()
203 if problems:
204 return 1
205 else:
206 return 0
209 if __name__ == "__main__":
210 sys.exit(main(sys.argv))