]>
Commit | Line | Data |
---|---|---|
c599d5cc | 1 | #!/usr/bin/env python |
ebba2af6 | 2 | # Copyright (c) 2016, 2017 Red Hat, Inc. |
c599d5cc AC |
3 | # |
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | # you may not use this file except in compliance with the License. | |
6 | # You may obtain a copy of the License at: | |
7 | # | |
8 | # http://www.apache.org/licenses/LICENSE-2.0 | |
9 | # | |
10 | # Unless required by applicable law or agreed to in writing, software | |
11 | # distributed under the License is distributed on an "AS IS" BASIS, | |
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | # See the License for the specific language governing permissions and | |
14 | # limitations under the License. | |
15 | from __future__ import print_function | |
16 | ||
17 | import email | |
18 | import getopt | |
19 | import re | |
20 | import sys | |
21 | ||
22 | __errors = 0 | |
23 | __warnings = 0 | |
3239c793 | 24 | print_file_name = None |
fb9410d8 | 25 | checking_file = False |
ebba2af6 AC |
26 | total_line = 0 |
27 | colors = False | |
3239c793 AC |
28 | |
29 | ||
ebba2af6 AC |
30 | def get_color_end(): |
31 | global colors | |
32 | if colors: | |
33 | return "\033[00m" | |
34 | return "" | |
c599d5cc AC |
35 | |
36 | ||
ebba2af6 AC |
37 | def get_red_begin(): |
38 | global colors | |
39 | if colors: | |
40 | return "\033[91m" | |
41 | return "" | |
42 | ||
43 | ||
44 | def get_yellow_begin(): | |
45 | global colors | |
46 | if colors: | |
47 | return "\033[93m" | |
48 | return "" | |
49 | ||
50 | ||
51 | def print_error(message): | |
c599d5cc | 52 | global __errors |
ebba2af6 | 53 | print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message)) |
c599d5cc AC |
54 | |
55 | __errors = __errors + 1 | |
56 | ||
57 | ||
ebba2af6 | 58 | def print_warning(message): |
c599d5cc | 59 | global __warnings |
ebba2af6 | 60 | print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message)) |
c599d5cc AC |
61 | |
62 | __warnings = __warnings + 1 | |
63 | ||
64 | ||
b537de13 BP |
65 | # These are keywords whose names are normally followed by a space and |
66 | # something in parentheses (usually an expression) then a left curly brace. | |
67 | # | |
68 | # 'do' almost qualifies but it's also used as "do { ... } while (...);". | |
69 | __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*' | |
70 | ||
c599d5cc | 71 | __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*') |
4d7f5e51 | 72 | __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*') |
c599d5cc AC |
73 | __regex_leading_with_whitespace_at_all = re.compile(r'^\s+') |
74 | __regex_leading_with_spaces = re.compile(r'^ +[\S]+') | |
75 | __regex_trailing_whitespace = re.compile(r'[^\S]+$') | |
c61e93d6 | 76 | __regex_single_line_feed = re.compile(r'^\f$') |
b537de13 BP |
77 | __regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]' |
78 | % __parenthesized_constructs) | |
79 | __regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]' | |
80 | % __parenthesized_constructs) | |
a1193b4d | 81 | __regex_for_if_parens_whitespace = \ |
b537de13 | 82 | re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs) |
30c7ffd5 | 83 | __regex_is_for_if_single_line_bracket = \ |
b537de13 | 84 | re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs) |
a1193b4d AC |
85 | __regex_ends_with_bracket = \ |
86 | re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$') | |
d56ec3bc | 87 | __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]') |
c599d5cc AC |
88 | |
89 | skip_leading_whitespace_check = False | |
90 | skip_trailing_whitespace_check = False | |
91 | skip_block_whitespace_check = False | |
92 | skip_signoff_check = False | |
93 | ||
6982ee96 JS |
94 | # Don't enforce character limit on files that include these characters in their |
95 | # name, as they may have legitimate reasons to have longer lines. | |
96 | # | |
97 | # Python isn't checked as flake8 performs these checks during build. | |
98 | line_length_blacklist = ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch', | |
99 | '.py'] | |
100 | ||
c599d5cc | 101 | |
4d7f5e51 AC |
102 | def is_subtracted_line(line): |
103 | """Returns TRUE if the line in question has been removed.""" | |
104 | return __regex_subtracted_line.search(line) is not None | |
105 | ||
fb9410d8 | 106 | |
c599d5cc AC |
107 | def is_added_line(line): |
108 | """Returns TRUE if the line in question is an added line. | |
109 | """ | |
fb9410d8 AC |
110 | global checking_file |
111 | return __regex_added_line.search(line) is not None or checking_file | |
112 | ||
113 | ||
114 | def added_line(line): | |
115 | """Returns the line formatted properly by removing diff syntax""" | |
116 | global checking_file | |
117 | if not checking_file: | |
118 | return line[1:] | |
119 | return line | |
c599d5cc AC |
120 | |
121 | ||
122 | def leading_whitespace_is_spaces(line): | |
123 | """Returns TRUE if the leading whitespace in added lines is spaces | |
124 | """ | |
125 | if skip_leading_whitespace_check: | |
126 | return True | |
c61e93d6 DDP |
127 | if (__regex_leading_with_whitespace_at_all.search(line) is not None and |
128 | __regex_single_line_feed.search(line) is None): | |
c599d5cc | 129 | return __regex_leading_with_spaces.search(line) is not None |
c61e93d6 | 130 | |
c599d5cc AC |
131 | return True |
132 | ||
133 | ||
134 | def trailing_whitespace_or_crlf(line): | |
135 | """Returns TRUE if the trailing characters is whitespace | |
136 | """ | |
137 | if skip_trailing_whitespace_check: | |
138 | return False | |
c61e93d6 DDP |
139 | return (__regex_trailing_whitespace.search(line) is not None and |
140 | __regex_single_line_feed.search(line) is None) | |
c599d5cc AC |
141 | |
142 | ||
143 | def if_and_for_whitespace_checks(line): | |
144 | """Return TRUE if there is appropriate whitespace after if, for, while | |
145 | """ | |
146 | if skip_block_whitespace_check: | |
147 | return True | |
148 | if (__regex_for_if_missing_whitespace.search(line) is not None or | |
149 | __regex_for_if_too_much_whitespace.search(line) is not None or | |
150 | __regex_for_if_parens_whitespace.search(line)): | |
151 | return False | |
152 | return True | |
153 | ||
154 | ||
30c7ffd5 AC |
155 | def if_and_for_end_with_bracket_check(line): |
156 | """Return TRUE if there is not a bracket at the end of an if, for, while | |
157 | block which fits on a single line ie: 'if (foo)'""" | |
158 | ||
159 | def balanced_parens(line): | |
160 | """This is a rather naive counter - it won't deal with quotes""" | |
161 | balance = 0 | |
162 | for letter in line: | |
973496b9 | 163 | if letter == '(': |
30c7ffd5 | 164 | balance += 1 |
973496b9 | 165 | elif letter == ')': |
30c7ffd5 | 166 | balance -= 1 |
973496b9 | 167 | return balance == 0 |
30c7ffd5 AC |
168 | |
169 | if __regex_is_for_if_single_line_bracket.search(line) is not None: | |
170 | if not balanced_parens(line): | |
171 | return True | |
172 | if __regex_ends_with_bracket.search(line) is None: | |
173 | return False | |
174 | return True | |
175 | ||
176 | ||
6fc2799e JS |
177 | def pointer_whitespace_check(line): |
178 | """Return TRUE if there is no space between a pointer name and the | |
179 | asterisk that denotes this is a apionter type, ie: 'struct foo*'""" | |
180 | return __regex_ptr_declaration_missing_whitespace.search(line) is not None | |
181 | ||
182 | ||
517f04ad AC |
183 | def line_length_check(line): |
184 | """Return TRUE if the line length is too long""" | |
185 | if len(line) > 79: | |
186 | return True | |
187 | return False | |
188 | ||
189 | ||
190 | checks = [ | |
191 | {'regex': None, | |
192 | 'match_name': | |
193 | lambda x: not any([fmt in x for fmt in line_length_blacklist]), | |
194 | 'check': lambda x: line_length_check(x), | |
ebba2af6 | 195 | 'print': lambda: print_warning("Line length is >79-characters long")}, |
907848bd AC |
196 | |
197 | {'regex': '$(?<!\.mk)', | |
198 | 'match_name': None, | |
199 | 'check': lambda x: not leading_whitespace_is_spaces(x), | |
ebba2af6 | 200 | 'print': lambda: print_warning("Line has non-spaces leading whitespace")}, |
907848bd AC |
201 | |
202 | {'regex': None, 'match_name': None, | |
203 | 'check': lambda x: trailing_whitespace_or_crlf(x), | |
ebba2af6 | 204 | 'print': lambda: print_warning("Line has trailing whitespace")}, |
907848bd AC |
205 | |
206 | {'regex': '(.c|.h)(.in)?$', 'match_name': None, | |
207 | 'check': lambda x: not if_and_for_whitespace_checks(x), | |
ebba2af6 | 208 | 'print': lambda: print_error("Improper whitespace around control block")}, |
907848bd AC |
209 | |
210 | {'regex': '(.c|.h)(.in)?$', 'match_name': None, | |
211 | 'check': lambda x: not if_and_for_end_with_bracket_check(x), | |
ebba2af6 | 212 | 'print': lambda: print_error("Inappropriate bracing around statement")}, |
907848bd AC |
213 | |
214 | {'regex': '(.c|.h)(.in)?$', 'match_name': None, | |
215 | 'check': lambda x: pointer_whitespace_check(x), | |
216 | 'print': | |
ebba2af6 | 217 | lambda: print_error("Inappropriate spacing in pointer declaration")} |
517f04ad AC |
218 | ] |
219 | ||
220 | ||
b95d82bf | 221 | def regex_function_factory(func_name): |
6ecf961e | 222 | regex = re.compile(r'\b%s\([^)]*\)' % func_name) |
b95d82bf JS |
223 | return lambda x: regex.search(x) is not None |
224 | ||
225 | ||
226 | def regex_error_factory(description): | |
227 | return lambda: print_error(description) | |
228 | ||
229 | ||
230 | std_functions = [ | |
231 | ('malloc', 'Use xmalloc() in place of malloc()'), | |
232 | ('calloc', 'Use xcalloc() in place of calloc()'), | |
233 | ('realloc', 'Use xrealloc() in place of realloc()'), | |
234 | ('strdup', 'Use xstrdup() in place of strdup()'), | |
235 | ('asprintf', 'Use xasprintf() in place of asprintf()'), | |
236 | ('vasprintf', 'Use xvasprintf() in place of vasprintf()'), | |
237 | ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'), | |
238 | ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'), | |
239 | ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'), | |
240 | ('strerror', 'Use ovs_strerror() in place of strerror()'), | |
241 | ('sleep', 'Use xsleep() in place of sleep()'), | |
242 | ('abort', 'Use ovs_abort() in place of abort()'), | |
243 | ('error', 'Use ovs_error() in place of error()'), | |
244 | ] | |
245 | checks += [ | |
246 | {'regex': '(.c|.h)(.in)?$', | |
247 | 'match_name': None, | |
248 | 'check': regex_function_factory(function_name), | |
249 | 'print': regex_error_factory(description)} | |
250 | for (function_name, description) in std_functions] | |
251 | ||
252 | ||
517f04ad AC |
253 | def get_file_type_checks(filename): |
254 | """Returns the list of checks for a file based on matching the filename | |
255 | against regex.""" | |
256 | global checks | |
257 | checkList = [] | |
258 | for check in checks: | |
259 | if check['regex'] is None and check['match_name'] is None: | |
260 | checkList.append(check) | |
261 | if check['regex'] is not None and \ | |
262 | re.compile(check['regex']).search(filename) is not None: | |
263 | checkList.append(check) | |
264 | elif check['match_name'] is not None and check['match_name'](filename): | |
265 | checkList.append(check) | |
266 | return checkList | |
267 | ||
268 | ||
269 | def run_checks(current_file, line, lineno): | |
270 | """Runs the various checks for the particular line. This will take | |
271 | filename into account.""" | |
ebba2af6 | 272 | global checking_file, total_line |
a84a1edb | 273 | print_line = False |
517f04ad AC |
274 | for check in get_file_type_checks(current_file): |
275 | if check['check'](line): | |
ebba2af6 | 276 | check['print']() |
a84a1edb AC |
277 | print_line = True |
278 | ||
279 | if print_line: | |
ebba2af6 AC |
280 | if checking_file: |
281 | print("%s:%d:" % (current_file, lineno)) | |
282 | else: | |
283 | print("#%d FILE: %s:%d:" % (total_line, current_file, lineno)) | |
284 | print("%s\n" % line) | |
517f04ad AC |
285 | |
286 | ||
95bd35d3 BP |
287 | def ovs_checkpatch_parse(text, filename): |
288 | global print_file_name, total_line, checking_file | |
c599d5cc AC |
289 | lineno = 0 |
290 | signatures = [] | |
291 | co_authors = [] | |
292 | parse = 0 | |
95bd35d3 | 293 | current_file = filename if checking_file else '' |
6982ee96 | 294 | previous_file = '' |
c599d5cc AC |
295 | scissors = re.compile(r'^[\w]*---[\w]*') |
296 | hunks = re.compile('^(---|\+\+\+) (\S+)') | |
4d7f5e51 AC |
297 | hunk_differences = re.compile( |
298 | r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@') | |
c599d5cc AC |
299 | is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$', |
300 | re.I | re.M | re.S) | |
301 | is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$', | |
302 | re.I | re.M | re.S) | |
303 | ||
0b93d978 | 304 | for line in text.decode(errors='ignore').split('\n'): |
6982ee96 JS |
305 | if current_file != previous_file: |
306 | previous_file = current_file | |
6982ee96 | 307 | |
c599d5cc | 308 | lineno = lineno + 1 |
ebba2af6 | 309 | total_line = total_line + 1 |
c599d5cc AC |
310 | if len(line) <= 0: |
311 | continue | |
312 | ||
fb9410d8 AC |
313 | if checking_file: |
314 | parse = 2 | |
315 | ||
c599d5cc AC |
316 | if parse == 1: |
317 | match = hunks.match(line) | |
318 | if match: | |
319 | parse = parse + 1 | |
2797ff00 | 320 | current_file = match.group(2)[2:] |
3239c793 | 321 | print_file_name = current_file |
c599d5cc AC |
322 | continue |
323 | elif parse == 0: | |
324 | if scissors.match(line): | |
325 | parse = parse + 1 | |
326 | if not skip_signoff_check: | |
327 | if len(signatures) == 0: | |
328 | print_error("No signatures found.") | |
83f76d4e | 329 | elif len(signatures) != 1 + len(co_authors): |
c599d5cc AC |
330 | print_error("Too many signoffs; " |
331 | "are you missing Co-authored-by lines?") | |
332 | if not set(co_authors) <= set(signatures): | |
333 | print_error("Co-authored-by/Signed-off-by corruption") | |
334 | elif is_signature.match(line): | |
335 | m = is_signature.match(line) | |
336 | signatures.append(m.group(3)) | |
337 | elif is_co_author.match(line): | |
338 | m = is_co_author.match(line) | |
339 | co_authors.append(m.group(3)) | |
340 | elif parse == 2: | |
c599d5cc AC |
341 | newfile = hunks.match(line) |
342 | if newfile: | |
bbbe2fa2 | 343 | current_file = newfile.group(2)[2:] |
3239c793 | 344 | print_file_name = current_file |
c599d5cc | 345 | continue |
4d7f5e51 AC |
346 | reset_line_number = hunk_differences.match(line) |
347 | if reset_line_number: | |
348 | lineno = int(reset_line_number.group(3)) | |
349 | if lineno < 0: | |
350 | lineno = -1 * lineno | |
351 | lineno -= 1 | |
352 | if is_subtracted_line(line): | |
353 | lineno -= 1 | |
c599d5cc AC |
354 | if not is_added_line(line): |
355 | continue | |
fb9410d8 AC |
356 | |
357 | cmp_line = added_line(line) | |
358 | ||
c599d5cc AC |
359 | # Skip files which have /datapath in them, since they are |
360 | # linux or windows coding standards | |
2797ff00 | 361 | if current_file.startswith('datapath'): |
c599d5cc | 362 | continue |
4f74db48 JS |
363 | if current_file.startswith('include/linux'): |
364 | continue | |
517f04ad | 365 | run_checks(current_file, cmp_line, lineno) |
c599d5cc AC |
366 | if __errors or __warnings: |
367 | return -1 | |
368 | return 0 | |
369 | ||
370 | ||
371 | def usage(): | |
372 | print("Open vSwitch checkpatch.py") | |
373 | print("Checks a patch for trivial mistakes.") | |
374 | print("usage:") | |
375 | print("%s [options] [patch file]" % sys.argv[0]) | |
376 | print("options:") | |
377 | print("-h|--help\t\t\t\tThis help message") | |
378 | print("-b|--skip-block-whitespace\t" | |
379 | "Skips the if/while/for whitespace tests") | |
fb9410d8 | 380 | print("-f|--check-file\t\t\tCheck a file instead of a patchfile.") |
c599d5cc AC |
381 | print("-l|--skip-leading-whitespace\t" |
382 | "Skips the leading whitespace test") | |
383 | print("-s|--skip-signoff-lines\t" | |
384 | "Do not emit an error if no Signed-off-by line is present") | |
385 | print("-t|--skip-trailing-whitespace\t" | |
386 | "Skips the trailing whitespace test") | |
387 | ||
a5e9c53c | 388 | |
c599d5cc | 389 | def ovs_checkpatch_file(filename): |
ebba2af6 | 390 | global __warnings, __errors, checking_file, total_line |
c599d5cc AC |
391 | try: |
392 | mail = email.message_from_file(open(filename, 'r')) | |
393 | except: | |
394 | print_error("Unable to parse file '%s'. Is it a patch?" % filename) | |
395 | return -1 | |
396 | ||
397 | for part in mail.walk(): | |
398 | if part.get_content_maintype() == 'multipart': | |
399 | continue | |
95bd35d3 | 400 | result = ovs_checkpatch_parse(part.get_payload(decode=True), filename) |
7d6b834f | 401 | if result < 0: |
ebba2af6 AC |
402 | print("Lines checked: %d, Warnings: %d, Errors: %d" % |
403 | (total_line, __warnings, __errors)) | |
404 | else: | |
405 | print("Lines checked: %d, no obvious problems found" % (total_line)) | |
7d6b834f | 406 | return result |
c599d5cc | 407 | |
884e0dfe | 408 | |
c599d5cc AC |
409 | if __name__ == '__main__': |
410 | try: | |
fb9410d8 AC |
411 | optlist, args = getopt.getopt(sys.argv[1:], 'bhlstf', |
412 | ["check-file", | |
413 | "help", | |
c599d5cc AC |
414 | "skip-block-whitespace", |
415 | "skip-leading-whitespace", | |
416 | "skip-signoff-lines", | |
417 | "skip-trailing-whitespace"]) | |
418 | except: | |
419 | print("Unknown option encountered. Please rerun with -h for help.") | |
420 | sys.exit(-1) | |
421 | ||
422 | for o, a in optlist: | |
423 | if o in ("-h", "--help"): | |
424 | usage() | |
425 | sys.exit(0) | |
426 | elif o in ("-b", "--skip-block-whitespace"): | |
427 | skip_block_whitespace_check = True | |
428 | elif o in ("-l", "--skip-leading-whitespace"): | |
429 | skip_leading_whitespace_check = True | |
430 | elif o in ("-s", "--skip-signoff-lines"): | |
431 | skip_signoff_check = True | |
432 | elif o in ("-t", "--skip-trailing-whitespace"): | |
433 | skip_trailing_whitespace_check = True | |
fb9410d8 AC |
434 | elif o in ("-f", "--check-file"): |
435 | checking_file = True | |
c599d5cc AC |
436 | else: |
437 | print("Unknown option '%s'" % o) | |
438 | sys.exit(-1) | |
ebba2af6 AC |
439 | |
440 | if sys.stdout.isatty(): | |
441 | colors = True | |
442 | ||
c599d5cc AC |
443 | try: |
444 | filename = args[0] | |
445 | except: | |
446 | if sys.stdin.isatty(): | |
447 | usage() | |
448 | sys.exit(-1) | |
95bd35d3 | 449 | sys.exit(ovs_checkpatch_parse(sys.stdin.read()), '-') |
c599d5cc | 450 | sys.exit(ovs_checkpatch_file(filename)) |