]> git.proxmox.com Git - mirror_ovs.git/blob - utilities/checkpatch.py
checkpatch.py: Fix Python style.
[mirror_ovs.git] / utilities / checkpatch.py
1 #!/usr/bin/env python
2 # Copyright (c) 2016, 2017 Red Hat, Inc.
3 # Copyright (c) 2018 Nicira, Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 from __future__ import print_function
17
18 import email
19 import getopt
20 import os
21 import re
22 import sys
23
24 __errors = 0
25 __warnings = 0
26 print_file_name = None
27 checking_file = False
28 total_line = 0
29 colors = False
30
31
32 def get_color_end():
33 global colors
34 if colors:
35 return "\033[00m"
36 return ""
37
38
39 def get_red_begin():
40 global colors
41 if colors:
42 return "\033[91m"
43 return ""
44
45
46 def get_yellow_begin():
47 global colors
48 if colors:
49 return "\033[93m"
50 return ""
51
52
53 def print_error(message):
54 global __errors
55 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message))
56
57 __errors = __errors + 1
58
59
60 def print_warning(message):
61 global __warnings
62 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message))
63
64 __warnings = __warnings + 1
65
66
67 def reset_counters():
68 global __errors, __warnings, total_line
69
70 __errors = 0
71 __warnings = 0
72 total_line = 0
73
74
75 # These are keywords whose names are normally followed by a space and
76 # something in parentheses (usually an expression) then a left curly brace.
77 #
78 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
79 __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
80
81 __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
82 __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
83 __regex_leading_with_whitespace_at_all = re.compile(r'^\s+')
84 __regex_leading_with_spaces = re.compile(r'^ +[\S]+')
85 __regex_trailing_whitespace = re.compile(r'[^\S]+$')
86 __regex_single_line_feed = re.compile(r'^\f$')
87 __regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]'
88 % __parenthesized_constructs)
89 __regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]'
90 % __parenthesized_constructs)
91 __regex_for_if_parens_whitespace = \
92 re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs)
93 __regex_is_for_if_single_line_bracket = \
94 re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs)
95 __regex_ends_with_bracket = \
96 re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
97 __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]')
98 __regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)')
99 __regex_has_comment = re.compile(r'.*(/\*|\*\s)')
100 __regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$')
101 __regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$')
102 __regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$')
103 __regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE)
104
105 skip_leading_whitespace_check = False
106 skip_trailing_whitespace_check = False
107 skip_block_whitespace_check = False
108 skip_signoff_check = False
109
110 # Don't enforce character limit on files that include these characters in their
111 # name, as they may have legitimate reasons to have longer lines.
112 #
113 # Python isn't checked as flake8 performs these checks during build.
114 line_length_blacklist = ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
115 '.py']
116
117 # Don't enforce a requirement that leading whitespace be all spaces on
118 # files that include these characters in their name, since these kinds
119 # of files need lines with leading tabs.
120 leading_whitespace_blacklist = ['.mk', '.am', '.at']
121
122
123 def is_subtracted_line(line):
124 """Returns TRUE if the line in question has been removed."""
125 return __regex_subtracted_line.search(line) is not None
126
127
128 def is_added_line(line):
129 """Returns TRUE if the line in question is an added line.
130 """
131 global checking_file
132 return __regex_added_line.search(line) is not None or checking_file
133
134
135 def added_line(line):
136 """Returns the line formatted properly by removing diff syntax"""
137 global checking_file
138 if not checking_file:
139 return line[1:]
140 return line
141
142
143 def leading_whitespace_is_spaces(line):
144 """Returns TRUE if the leading whitespace in added lines is spaces
145 """
146 if skip_leading_whitespace_check:
147 return True
148 if (__regex_leading_with_whitespace_at_all.search(line) is not None and
149 __regex_single_line_feed.search(line) is None):
150 return __regex_leading_with_spaces.search(line) is not None
151
152 return True
153
154
155 def trailing_whitespace_or_crlf(line):
156 """Returns TRUE if the trailing characters is whitespace
157 """
158 if skip_trailing_whitespace_check:
159 return False
160 return (__regex_trailing_whitespace.search(line) is not None and
161 __regex_single_line_feed.search(line) is None)
162
163
164 def if_and_for_whitespace_checks(line):
165 """Return TRUE if there is appropriate whitespace after if, for, while
166 """
167 if skip_block_whitespace_check:
168 return True
169 if (__regex_for_if_missing_whitespace.search(line) is not None or
170 __regex_for_if_too_much_whitespace.search(line) is not None or
171 __regex_for_if_parens_whitespace.search(line)):
172 return False
173 return True
174
175
176 def if_and_for_end_with_bracket_check(line):
177 """Return TRUE if there is not a bracket at the end of an if, for, while
178 block which fits on a single line ie: 'if (foo)'"""
179
180 def balanced_parens(line):
181 """This is a rather naive counter - it won't deal with quotes"""
182 balance = 0
183 for letter in line:
184 if letter == '(':
185 balance += 1
186 elif letter == ')':
187 balance -= 1
188 return balance == 0
189
190 if __regex_is_for_if_single_line_bracket.search(line) is not None:
191 if not balanced_parens(line):
192 return True
193 if __regex_ends_with_bracket.search(line) is None:
194 return False
195 if __regex_conditional_else_bracing.match(line) is not None:
196 return False
197 if __regex_conditional_else_bracing2.match(line) is not None:
198 return False
199 return True
200
201
202 def pointer_whitespace_check(line):
203 """Return TRUE if there is no space between a pointer name and the
204 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
205 return __regex_ptr_declaration_missing_whitespace.search(line) is not None
206
207
208 def line_length_check(line):
209 """Return TRUE if the line length is too long"""
210 if len(line) > 79:
211 return True
212 return False
213
214
215 def is_comment_line(line):
216 """Returns TRUE if the current line is part of a block comment."""
217 return __regex_is_comment_line.match(line) is not None
218
219
220 def has_comment(line):
221 """Returns TRUE if the current line contains a comment or is part of
222 a block comment."""
223 return __regex_has_comment.match(line) is not None
224
225
226 def trailing_operator(line):
227 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
228 return __regex_trailing_operator.match(line) is not None
229
230
231 def has_xxx_mark(line):
232 """Returns TRUE if the current line contains 'xxx'."""
233 return __regex_has_xxx_mark.match(line) is not None
234
235
236 checks = [
237 {'regex': None,
238 'match_name':
239 lambda x: not any([fmt in x for fmt in line_length_blacklist]),
240 'check': lambda x: line_length_check(x),
241 'print': lambda: print_warning("Line length is >79-characters long")},
242
243 {'regex': None,
244 'match_name':
245 lambda x: not any([fmt in x for fmt in leading_whitespace_blacklist]),
246 'check': lambda x: not leading_whitespace_is_spaces(x),
247 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
248
249 {'regex': None, 'match_name': None,
250 'check': lambda x: trailing_whitespace_or_crlf(x),
251 'print': lambda: print_warning("Line has trailing whitespace")},
252
253 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
254 'prereq': lambda x: not is_comment_line(x),
255 'check': lambda x: not if_and_for_whitespace_checks(x),
256 'print': lambda: print_error("Improper whitespace around control block")},
257
258 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
259 'prereq': lambda x: not is_comment_line(x),
260 'check': lambda x: not if_and_for_end_with_bracket_check(x),
261 'print': lambda: print_error("Inappropriate bracing around statement")},
262
263 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
264 'prereq': lambda x: not is_comment_line(x),
265 'check': lambda x: pointer_whitespace_check(x),
266 'print':
267 lambda: print_error("Inappropriate spacing in pointer declaration")},
268
269 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
270 'prereq': lambda x: not is_comment_line(x),
271 'check': lambda x: trailing_operator(x),
272 'print':
273 lambda: print_error("Line has '?' or ':' operator at end of line")},
274
275 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
276 'prereq': lambda x: has_comment(x),
277 'check': lambda x: has_xxx_mark(x),
278 'print': lambda: print_warning("Comment with 'xxx' marker")},
279 ]
280
281
282 def regex_function_factory(func_name):
283 regex = re.compile(r'\b%s\([^)]*\)' % func_name)
284 return lambda x: regex.search(x) is not None
285
286
287 def regex_error_factory(description):
288 return lambda: print_error(description)
289
290
291 std_functions = [
292 ('malloc', 'Use xmalloc() in place of malloc()'),
293 ('calloc', 'Use xcalloc() in place of calloc()'),
294 ('realloc', 'Use xrealloc() in place of realloc()'),
295 ('strdup', 'Use xstrdup() in place of strdup()'),
296 ('asprintf', 'Use xasprintf() in place of asprintf()'),
297 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
298 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
299 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
300 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
301 ('strerror', 'Use ovs_strerror() in place of strerror()'),
302 ('sleep', 'Use xsleep() in place of sleep()'),
303 ('abort', 'Use ovs_abort() in place of abort()'),
304 ('assert', 'Use ovs_assert() in place of assert()'),
305 ('error', 'Use ovs_error() in place of error()'),
306 ]
307 checks += [
308 {'regex': '(\.c|\.h)(\.in)?$',
309 'match_name': None,
310 'prereq': lambda x: not is_comment_line(x),
311 'check': regex_function_factory(function_name),
312 'print': regex_error_factory(description)}
313 for (function_name, description) in std_functions]
314
315
316 def regex_operator_factory(operator):
317 regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator)
318 return lambda x: regex.search(x) is not None
319
320
321 infix_operators = \
322 [re.escape(op) for op in ['/', '%', '<<', '>>', '<=', '>=', '==', '!=',
323 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
324 '&=', '^=', '|=', '<<=', '>>=']] \
325 + ['[^<" ]<[^=" ]', '[^->" ]>[^=" ]', '[^ !()/"]\*[^/]', '[^ !&()"]&',
326 '[^" +(]\+[^"+;]', '[^" -(]-[^"->;]', '[^" <>=!^|+\-*/%&]=[^"=]']
327 checks += [
328 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
329 'prereq': lambda x: not is_comment_line(x),
330 'check': regex_operator_factory(operator),
331 'print': lambda: print_warning("Line lacks whitespace around operator")}
332 for operator in infix_operators]
333
334
335 def get_file_type_checks(filename):
336 """Returns the list of checks for a file based on matching the filename
337 against regex."""
338 global checks
339 checkList = []
340 for check in checks:
341 if check['regex'] is None and check['match_name'] is None:
342 checkList.append(check)
343 if check['regex'] is not None and \
344 re.compile(check['regex']).search(filename) is not None:
345 checkList.append(check)
346 elif check['match_name'] is not None and check['match_name'](filename):
347 checkList.append(check)
348 return checkList
349
350
351 def run_checks(current_file, line, lineno):
352 """Runs the various checks for the particular line. This will take
353 filename into account."""
354 global checking_file, total_line
355 print_line = False
356 for check in get_file_type_checks(current_file):
357 if 'prereq' in check and not check['prereq'](line):
358 continue
359 if check['check'](line):
360 check['print']()
361 print_line = True
362
363 if print_line:
364 if checking_file:
365 print("%s:%d:" % (current_file, lineno))
366 else:
367 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
368 print("%s\n" % line)
369
370
371 def ovs_checkpatch_parse(text, filename):
372 global print_file_name, total_line, checking_file
373 lineno = 0
374 signatures = []
375 co_authors = []
376 parse = 0
377 current_file = filename if checking_file else ''
378 previous_file = ''
379 scissors = re.compile(r'^[\w]*---[\w]*')
380 hunks = re.compile('^(---|\+\+\+) (\S+)')
381 hunk_differences = re.compile(
382 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
383 is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$',
384 re.I | re.M | re.S)
385 is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$',
386 re.I | re.M | re.S)
387 is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$',
388 re.I | re.M | re.S)
389
390 reset_counters()
391
392 for line in text.split('\n'):
393 if current_file != previous_file:
394 previous_file = current_file
395
396 lineno = lineno + 1
397 total_line = total_line + 1
398 if len(line) <= 0:
399 continue
400
401 if checking_file:
402 parse = 2
403
404 if parse == 1:
405 match = hunks.match(line)
406 if match:
407 parse = parse + 1
408 current_file = match.group(2)[2:]
409 print_file_name = current_file
410 continue
411 elif parse == 0:
412 if scissors.match(line):
413 parse = parse + 1
414 if not skip_signoff_check:
415 if len(signatures) == 0:
416 print_error("No signatures found.")
417 elif len(signatures) != 1 + len(co_authors):
418 print_error("Too many signoffs; "
419 "are you missing Co-authored-by lines?")
420 if not set(co_authors) <= set(signatures):
421 print_error("Co-authored-by/Signed-off-by corruption")
422 elif is_signature.match(line):
423 m = is_signature.match(line)
424 signatures.append(m.group(3))
425 elif is_co_author.match(line):
426 m = is_co_author.match(line)
427 co_authors.append(m.group(3))
428 elif is_gerrit_change_id.match(line):
429 print_error(
430 "Remove Gerrit Change-Id's before submitting upstream.")
431 print("%d: %s\n" % (lineno, line))
432 elif parse == 2:
433 newfile = hunks.match(line)
434 if newfile:
435 current_file = newfile.group(2)[2:]
436 print_file_name = current_file
437 continue
438 reset_line_number = hunk_differences.match(line)
439 if reset_line_number:
440 lineno = int(reset_line_number.group(3))
441 if lineno < 0:
442 lineno = -1 * lineno
443 lineno -= 1
444 if is_subtracted_line(line):
445 lineno -= 1
446 if not is_added_line(line):
447 continue
448
449 cmp_line = added_line(line)
450
451 # Skip files which have /datapath in them, since they are
452 # linux or windows coding standards
453 if current_file.startswith('datapath'):
454 continue
455 if current_file.startswith('include/linux'):
456 continue
457 run_checks(current_file, cmp_line, lineno)
458 if __errors or __warnings:
459 return -1
460 return 0
461
462
463 def usage():
464 print("""\
465 Open vSwitch checkpatch.py
466 Checks a patch for trivial mistakes.
467 usage:
468 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
469
470 Input options:
471 -f|--check-file Arguments are source files, not patches.
472 -1, -2, ... Check recent commits in this repo.
473
474 Check options:
475 -h|--help This help message
476 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
477 -l|--skip-leading-whitespace Skips the leading whitespace test
478 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
479 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
480 % sys.argv[0])
481
482
483 def ovs_checkpatch_print_result(result):
484 global __warnings, __errors, total_line
485 if result < 0:
486 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
487 (total_line, __warnings, __errors))
488 else:
489 print("Lines checked: %d, no obvious problems found\n" % (total_line))
490
491
492 def ovs_checkpatch_file(filename):
493 try:
494 mail = email.message_from_file(open(filename, 'r'))
495 except:
496 print_error("Unable to parse file '%s'. Is it a patch?" % filename)
497 return -1
498
499 for part in mail.walk():
500 if part.get_content_maintype() == 'multipart':
501 continue
502 result = ovs_checkpatch_parse(part.get_payload(decode=False), filename)
503 ovs_checkpatch_print_result(result)
504 return result
505
506
507 def partition(pred, iterable):
508 """Returns [[trues], [falses]], where [trues] is the items in
509 'iterable' that satisfy 'pred' and [falses] is all the rest."""
510 trues = []
511 falses = []
512 for item in iterable:
513 if pred(item):
514 trues.append(item)
515 else:
516 falses.append(item)
517 return trues, falses
518
519
520 if __name__ == '__main__':
521 try:
522 numeric_options, args = partition(lambda s: re.match('-[0-9]+$', s),
523 sys.argv[1:])
524 n_patches = int(numeric_options[-1][1:]) if numeric_options else 0
525
526 optlist, args = getopt.getopt(args, 'bhlstf',
527 ["check-file",
528 "help",
529 "skip-block-whitespace",
530 "skip-leading-whitespace",
531 "skip-signoff-lines",
532 "skip-trailing-whitespace"])
533 except:
534 print("Unknown option encountered. Please rerun with -h for help.")
535 sys.exit(-1)
536
537 for o, a in optlist:
538 if o in ("-h", "--help"):
539 usage()
540 sys.exit(0)
541 elif o in ("-b", "--skip-block-whitespace"):
542 skip_block_whitespace_check = True
543 elif o in ("-l", "--skip-leading-whitespace"):
544 skip_leading_whitespace_check = True
545 elif o in ("-s", "--skip-signoff-lines"):
546 skip_signoff_check = True
547 elif o in ("-t", "--skip-trailing-whitespace"):
548 skip_trailing_whitespace_check = True
549 elif o in ("-f", "--check-file"):
550 checking_file = True
551 else:
552 print("Unknown option '%s'" % o)
553 sys.exit(-1)
554
555 if sys.stdout.isatty():
556 colors = True
557
558 if n_patches:
559 status = 0
560
561 git_log = 'git log --no-color --no-merges --pretty=format:"%H %s" '
562 with os.popen(git_log + '-%d' % n_patches, 'r') as f:
563 commits = f.read().split("\n")
564
565 for i in reversed(range(0, n_patches)):
566 revision, name = commits[i].split(" ", 1)
567 f = os.popen('git format-patch -1 --stdout %s' % revision, 'r')
568 patch = f.read()
569 f.close()
570
571 print('== Checking %s ("%s") ==' % (revision[0:12], name))
572 result = ovs_checkpatch_parse(patch, revision)
573 ovs_checkpatch_print_result(result)
574 if result:
575 status = -1
576 sys.exit(status)
577
578 if not args:
579 if sys.stdin.isatty():
580 usage()
581 sys.exit(-1)
582 result = ovs_checkpatch_parse(sys.stdin.read(), '-')
583 ovs_checkpatch_print_result(result)
584 sys.exit(result)
585
586 status = 0
587 for filename in args:
588 print('== Checking "%s" ==' % filename)
589 result = ovs_checkpatch_file(filename)
590 if result:
591 status = -1
592 sys.exit(status)