]> git.proxmox.com Git - mirror_ovs.git/blob - utilities/checkpatch.py
4164ea8ec6241f3a311ad38d21c94c655bfdf0c0
[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 RETURN_CHECK_INITIAL_STATE = 0
25 RETURN_CHECK_STATE_WITH_RETURN = 1
26 RETURN_CHECK_AWAITING_BRACE = 2
27 __errors = 0
28 __warnings = 0
29 empty_return_check_state = 0
30 print_file_name = None
31 checking_file = False
32 total_line = 0
33 colors = False
34 spellcheck_comments = False
35 quiet = False
36 spell_check_dict = None
37
38
39 def open_spell_check_dict():
40 import enchant
41
42 try:
43 extra_keywords = ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd',
44 'netdev', 'selinux', 'ovs-ctl', 'dpctl', 'ofctl',
45 'openvswitch', 'dpdk', 'hugepage', 'hugepages',
46 'pmd', 'upcall', 'vhost', 'rx', 'tx', 'vhostuser',
47 'openflow', 'qsort', 'rxq', 'txq', 'perf', 'stats',
48 'struct', 'int', 'char', 'bool', 'upcalls', 'nicira',
49 'bitmask', 'ipv4', 'ipv6', 'tcp', 'tcp4', 'tcpv4',
50 'udp', 'udp4', 'udpv4', 'icmp', 'icmp4', 'icmpv6',
51 'vlan', 'vxlan', 'cksum', 'csum', 'checksum',
52 'ofproto', 'numa', 'mempool', 'mempools', 'mbuf',
53 'mbufs', 'hmap', 'cmap', 'smap', 'dhcpv4', 'dhcp',
54 'dhcpv6', 'opts', 'metadata', 'geneve', 'mutex',
55 'netdev', 'netdevs', 'subtable', 'virtio', 'qos',
56 'policer', 'datapath', 'tunctl', 'attr', 'ethernet',
57 'ether', 'defrag', 'defragment', 'loopback', 'sflow',
58 'acl', 'initializer', 'recirc', 'xlated', 'unclosed',
59 'netlink', 'msec', 'usec', 'nsec', 'ms', 'us', 'ns',
60 'kilobits', 'kbps', 'kilobytes', 'megabytes', 'mbps',
61 'gigabytes', 'gbps', 'megabits', 'gigabits', 'pkts',
62 'tuple', 'miniflow', 'megaflow', 'conntrack',
63 'vlans', 'vxlans', 'arg', 'tpid', 'xbundle',
64 'xbundles', 'mbundle', 'mbundles', 'netflow',
65 'localnet', 'odp', 'pre', 'dst', 'dest', 'src',
66 'ethertype', 'cvlan', 'ips', 'msg', 'msgs',
67 'liveness', 'userspace', 'eventmask', 'datapaths',
68 'slowpath', 'fastpath', 'multicast', 'unicast',
69 'revalidation', 'namespace', 'qdisc', 'uuid',
70 'ofport', 'subnet', 'revalidation', 'revalidator',
71 'revalidate', 'l2', 'l3', 'l4', 'openssl', 'mtu',
72 'ifindex', 'enum', 'enums', 'http', 'https', 'num',
73 'vconn', 'vconns', 'conn', 'nat', 'memset', 'memcmp',
74 'strcmp', 'strcasecmp', 'tc', 'ufid', 'api',
75 'ofpbuf', 'ofpbufs', 'hashmaps', 'hashmap', 'deref',
76 'dereference', 'hw', 'prio', 'sendmmsg', 'sendmsg',
77 'malloc', 'free', 'alloc', 'pid', 'ppid', 'pgid',
78 'uid', 'gid', 'sid', 'utime', 'stime', 'cutime',
79 'cstime', 'vsize', 'rss', 'rsslim', 'whcan', 'gtime',
80 'eip', 'rip', 'cgtime', 'dbg', 'gw', 'sbrec', 'bfd',
81 'sizeof', 'pmds', 'nic', 'nics', 'hwol', 'encap',
82 'decap', 'tlv', 'tlvs', 'decapsulation', 'fd',
83 'cacheline', 'xlate', 'skiplist', 'idl',
84 'comparator', 'natting', 'alg', 'pasv', 'epasv',
85 'wildcard', 'nated', 'amd64', 'x86_64',
86 'recirculation']
87
88 global spell_check_dict
89 spell_check_dict = enchant.Dict("en_US")
90 for kw in extra_keywords:
91 spell_check_dict.add(kw)
92
93 return True
94 except:
95 return False
96
97
98 def get_color_end():
99 global colors
100 if colors:
101 return "\033[00m"
102 return ""
103
104
105 def get_red_begin():
106 global colors
107 if colors:
108 return "\033[91m"
109 return ""
110
111
112 def get_yellow_begin():
113 global colors
114 if colors:
115 return "\033[93m"
116 return ""
117
118
119 def print_error(message):
120 global __errors
121 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message))
122
123 __errors = __errors + 1
124
125
126 def print_warning(message):
127 global __warnings
128 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message))
129
130 __warnings = __warnings + 1
131
132
133 def reset_counters():
134 global __errors, __warnings, total_line
135
136 __errors = 0
137 __warnings = 0
138 total_line = 0
139
140
141 # These are keywords whose names are normally followed by a space and
142 # something in parentheses (usually an expression) then a left curly brace.
143 #
144 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
145 __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_*EACH[_A-Z]*'
146
147 __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
148 __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
149 __regex_leading_with_whitespace_at_all = re.compile(r'^\s+')
150 __regex_leading_with_spaces = re.compile(r'^ +[\S]+')
151 __regex_trailing_whitespace = re.compile(r'[^\S]+$')
152 __regex_single_line_feed = re.compile(r'^\f$')
153 __regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]'
154 % __parenthesized_constructs)
155 __regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]'
156 % __parenthesized_constructs)
157 __regex_for_if_parens_whitespace = \
158 re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs)
159 __regex_is_for_if_single_line_bracket = \
160 re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs)
161 __regex_ends_with_bracket = \
162 re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
163 __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]')
164 __regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)')
165 __regex_has_comment = re.compile(r'.*(/\*|\*\s)')
166 __regex_has_c99_comment = re.compile(r'.*//.*$')
167 __regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$')
168 __regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$')
169 __regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$')
170 __regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE)
171 __regex_added_doc_rst = re.compile(
172 r'\ndiff .*Documentation/.*rst\nnew file mode')
173 __regex_empty_return = re.compile(r'\s*return;')
174 __regex_if_macros = re.compile(r'^ +(%s) \([\S][\s\S]+[\S]\) { \\' %
175 __parenthesized_constructs)
176
177 skip_leading_whitespace_check = False
178 skip_trailing_whitespace_check = False
179 skip_block_whitespace_check = False
180 skip_signoff_check = False
181
182 # Don't enforce character limit on files that include these characters in their
183 # name, as they may have legitimate reasons to have longer lines.
184 #
185 # Python isn't checked as flake8 performs these checks during build.
186 line_length_blacklist = re.compile(
187 r'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules')
188
189 # Don't enforce a requirement that leading whitespace be all spaces on
190 # files that include these characters in their name, since these kinds
191 # of files need lines with leading tabs.
192 leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules')
193
194
195 def is_subtracted_line(line):
196 """Returns TRUE if the line in question has been removed."""
197 return __regex_subtracted_line.search(line) is not None
198
199
200 def is_added_line(line):
201 """Returns TRUE if the line in question is an added line.
202 """
203 global checking_file
204 return __regex_added_line.search(line) is not None or checking_file
205
206
207 def added_line(line):
208 """Returns the line formatted properly by removing diff syntax"""
209 global checking_file
210 if not checking_file:
211 return line[1:]
212 return line
213
214
215 def leading_whitespace_is_spaces(line):
216 """Returns TRUE if the leading whitespace in added lines is spaces
217 """
218 if skip_leading_whitespace_check:
219 return True
220 if (__regex_leading_with_whitespace_at_all.search(line) is not None and
221 __regex_single_line_feed.search(line) is None):
222 return __regex_leading_with_spaces.search(line) is not None
223
224 return True
225
226
227 def trailing_whitespace_or_crlf(line):
228 """Returns TRUE if the trailing characters is whitespace
229 """
230 if skip_trailing_whitespace_check:
231 return False
232 return (__regex_trailing_whitespace.search(line) is not None and
233 __regex_single_line_feed.search(line) is None)
234
235
236 def if_and_for_whitespace_checks(line):
237 """Return TRUE if there is appropriate whitespace after if, for, while
238 """
239 if skip_block_whitespace_check:
240 return True
241 if (__regex_for_if_missing_whitespace.search(line) is not None or
242 __regex_for_if_too_much_whitespace.search(line) is not None or
243 __regex_for_if_parens_whitespace.search(line)):
244 return False
245 return True
246
247
248 def if_and_for_end_with_bracket_check(line):
249 """Return TRUE if there is not a bracket at the end of an if, for, while
250 block which fits on a single line ie: 'if (foo)'"""
251
252 def balanced_parens(line):
253 """This is a rather naive counter - it won't deal with quotes"""
254 balance = 0
255 for letter in line:
256 if letter == '(':
257 balance += 1
258 elif letter == ')':
259 balance -= 1
260 return balance == 0
261
262 if __regex_is_for_if_single_line_bracket.search(line) is not None:
263 if not balanced_parens(line):
264 return True
265
266 if __regex_ends_with_bracket.search(line) is None and \
267 __regex_if_macros.match(line) is None:
268 return False
269 if __regex_conditional_else_bracing.match(line) is not None:
270 return False
271 if __regex_conditional_else_bracing2.match(line) is not None:
272 return False
273 return True
274
275
276 def pointer_whitespace_check(line):
277 """Return TRUE if there is no space between a pointer name and the
278 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
279 return __regex_ptr_declaration_missing_whitespace.search(line) is not None
280
281
282 def line_length_check(line):
283 """Return TRUE if the line length is too long"""
284 if len(line) > 79:
285 print_warning("Line is %d characters long (recommended limit is 79)"
286 % len(line))
287 return True
288 return False
289
290
291 def is_comment_line(line):
292 """Returns TRUE if the current line is part of a block comment."""
293 return __regex_is_comment_line.match(line) is not None
294
295
296 def has_comment(line):
297 """Returns TRUE if the current line contains a comment or is part of
298 a block comment."""
299 return __regex_has_comment.match(line) is not None
300
301
302 def has_c99_comment(line):
303 """Returns TRUE if the current line contains C99 style comment (//)."""
304 return __regex_has_c99_comment.match(line) is not None
305
306
307 def trailing_operator(line):
308 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
309 return __regex_trailing_operator.match(line) is not None
310
311
312 def has_xxx_mark(line):
313 """Returns TRUE if the current line contains 'xxx'."""
314 return __regex_has_xxx_mark.match(line) is not None
315
316
317 def filter_comments(current_line, keep=False):
318 """remove all of the c-style comments in a line"""
319 STATE_NORMAL = 0
320 STATE_COMMENT_SLASH = 1
321 STATE_COMMENT_CONTENTS = 3
322 STATE_COMMENT_END_SLASH = 4
323
324 state = STATE_NORMAL
325 sanitized_line = ''
326 check_state = STATE_NORMAL
327 only_whitespace = True
328
329 if keep:
330 check_state = STATE_COMMENT_CONTENTS
331
332 for c in current_line:
333 if c == '/':
334 if state == STATE_NORMAL:
335 state = STATE_COMMENT_SLASH
336 elif state == STATE_COMMENT_SLASH:
337 # This is for c++ style comments. We will warn later
338 return sanitized_line[:1]
339 elif state == STATE_COMMENT_END_SLASH:
340 c = ''
341 state = STATE_NORMAL
342 elif c == '*':
343 if only_whitespace:
344 # just assume this is a continuation from the previous line
345 # as a comment
346 state = STATE_COMMENT_END_SLASH
347 elif state == STATE_COMMENT_SLASH:
348 state = STATE_COMMENT_CONTENTS
349 sanitized_line = sanitized_line[:-1]
350 elif state == STATE_COMMENT_CONTENTS:
351 state = STATE_COMMENT_END_SLASH
352 elif state == STATE_COMMENT_END_SLASH:
353 # Need to re-introduce the star from the previous state, since
354 # it may have been clipped by the state check below.
355 c = '*' + c
356 state = STATE_COMMENT_CONTENTS
357 elif state == STATE_COMMENT_SLASH:
358 # Need to re-introduce the slash from the previous state, since
359 # it may have been clipped by the state check below.
360 c = '/' + c
361 state = STATE_NORMAL
362
363 if state != check_state:
364 c = ''
365
366 if not c.isspace():
367 only_whitespace = False
368
369 sanitized_line += c
370
371 return sanitized_line
372
373
374 def check_comment_spelling(line):
375 if not spell_check_dict or not spellcheck_comments:
376 return False
377
378 comment_words = filter_comments(line, True).replace(':', ' ').split(' ')
379 for word in comment_words:
380 skip = False
381 strword = re.subn(r'\W+', '', word)[0].replace(',', '')
382 if len(strword) and not spell_check_dict.check(strword.lower()):
383 if any([check_char in word
384 for check_char in ['=', '(', '-', '_', '/', '\'']]):
385 skip = True
386
387 # special case the '.'
388 if '.' in word and not word.endswith('.'):
389 skip = True
390
391 # skip proper nouns and references to macros
392 if strword.isupper() or (strword[0].isupper() and
393 strword[1:].islower()):
394 skip = True
395
396 # skip words that start with numbers
397 if strword.startswith(tuple('0123456789')):
398 skip = True
399
400 if not skip:
401 print_warning("Check for spelling mistakes (e.g. \"%s\")"
402 % strword)
403 return True
404
405 return False
406
407
408 def __check_doc_is_listed(text, doctype, docdir, docfile):
409 if doctype == 'rst':
410 beginre = re.compile(r'\+\+\+.*{}/index.rst'.format(docdir))
411 docre = re.compile(r'\n\+.*{}'.format(docfile.replace('.rst', '')))
412 elif doctype == 'automake':
413 beginre = re.compile(r'\+\+\+.*Documentation/automake.mk')
414 docre = re.compile(r'\n\+\t{}/{}'.format(docdir, docfile))
415 else:
416 raise NotImplementedError("Invalid doctype: {}".format(doctype))
417
418 res = beginre.search(text)
419 if res is None:
420 return True
421
422 hunkstart = res.span()[1]
423 hunkre = re.compile(r'\n(---|\+\+\+) (\S+)')
424 res = hunkre.search(text[hunkstart:])
425 if res is None:
426 hunkend = len(text)
427 else:
428 hunkend = hunkstart + res.span()[0]
429
430 hunk = text[hunkstart:hunkend]
431 # find if the file is being added.
432 if docre.search(hunk) is not None:
433 return False
434
435 return True
436
437
438 def __check_new_docs(text, doctype):
439 """Check if the documentation is listed properly. If doctype is 'rst' then
440 the index.rst is checked. If the doctype is 'automake' then automake.mk
441 is checked. Returns TRUE if the new file is not listed."""
442 failed = False
443 new_docs = __regex_added_doc_rst.findall(text)
444 for doc in new_docs:
445 docpathname = doc.split(' ')[2]
446 gitdocdir, docfile = os.path.split(docpathname.rstrip('\n'))
447 if docfile == "index.rst":
448 continue
449
450 if gitdocdir.startswith('a/'):
451 docdir = gitdocdir.replace('a/', '', 1)
452 else:
453 docdir = gitdocdir
454
455 if __check_doc_is_listed(text, doctype, docdir, docfile):
456 if doctype == 'rst':
457 print_warning("New doc {} not listed in {}/index.rst".format(
458 docfile, docdir))
459 elif doctype == 'automake':
460 print_warning("New doc {} not listed in "
461 "Documentation/automake.mk".format(docfile))
462 else:
463 raise NotImplementedError("Invalid doctype: {}".format(
464 doctype))
465
466 failed = True
467
468 return failed
469
470
471 def check_doc_docs_automake(text):
472 return __check_new_docs(text, 'automake')
473
474
475 def check_new_docs_index(text):
476 return __check_new_docs(text, 'rst')
477
478
479 def empty_return_with_brace(line):
480 """Returns TRUE if a function contains a return; followed
481 by one or more line feeds and terminates with a '}'
482 at start of line"""
483
484 def empty_return(line):
485 """Returns TRUE if a function has a 'return;'"""
486 return __regex_empty_return.match(line) is not None
487
488 global empty_return_check_state
489 if empty_return_check_state == RETURN_CHECK_INITIAL_STATE \
490 and empty_return(line):
491 empty_return_check_state = RETURN_CHECK_STATE_WITH_RETURN
492 elif empty_return_check_state == RETURN_CHECK_STATE_WITH_RETURN \
493 and (re.match(r'^}$', line) or len(line) == 0):
494 if re.match('^}$', line):
495 empty_return_check_state = RETURN_CHECK_AWAITING_BRACE
496 else:
497 empty_return_check_state = RETURN_CHECK_INITIAL_STATE
498
499 if empty_return_check_state == RETURN_CHECK_AWAITING_BRACE:
500 empty_return_check_state = RETURN_CHECK_INITIAL_STATE
501 return True
502
503 return False
504
505
506 file_checks = [
507 {'regex': __regex_added_doc_rst,
508 'check': check_new_docs_index},
509 {'regex': __regex_added_doc_rst,
510 'check': check_doc_docs_automake}
511 ]
512
513 checks = [
514 {'regex': None,
515 'match_name': lambda x: not line_length_blacklist.search(x),
516 'check': lambda x: line_length_check(x)},
517
518 {'regex': None,
519 'match_name': lambda x: not leading_whitespace_blacklist.search(x),
520 'check': lambda x: not leading_whitespace_is_spaces(x),
521 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
522
523 {'regex': None, 'match_name': None,
524 'check': lambda x: trailing_whitespace_or_crlf(x),
525 'print': lambda: print_warning("Line has trailing whitespace")},
526
527 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
528 'prereq': lambda x: not is_comment_line(x),
529 'check': lambda x: not if_and_for_whitespace_checks(x),
530 'print': lambda: print_error("Improper whitespace around control block")},
531
532 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
533 'prereq': lambda x: not is_comment_line(x),
534 'check': lambda x: not if_and_for_end_with_bracket_check(x),
535 'print': lambda: print_error("Inappropriate bracing around statement")},
536
537 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
538 'prereq': lambda x: not is_comment_line(x),
539 'check': lambda x: pointer_whitespace_check(x),
540 'print':
541 lambda: print_error("Inappropriate spacing in pointer declaration")},
542
543 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
544 'prereq': lambda x: not is_comment_line(x),
545 'check': lambda x: trailing_operator(x),
546 'print':
547 lambda: print_error("Line has '?' or ':' operator at end of line")},
548
549 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
550 'prereq': lambda x: has_comment(x),
551 'check': lambda x: has_xxx_mark(x),
552 'print': lambda: print_warning("Comment with 'xxx' marker")},
553
554 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
555 'prereq': lambda x: not is_comment_line(x),
556 'check': lambda x: has_c99_comment(x),
557 'print': lambda: print_error("C99 style comment")},
558
559 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
560 'prereq': lambda x: has_comment(x),
561 'check': lambda x: check_comment_spelling(x)},
562
563 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
564 'check': lambda x: empty_return_with_brace(x),
565 'interim_line': True,
566 'print':
567 lambda: print_warning("Empty return followed by brace, consider omitting")
568 },
569 ]
570
571
572 def regex_function_factory(func_name):
573 regex = re.compile(r'\b%s\([^)]*\)' % func_name)
574 return lambda x: regex.search(x) is not None
575
576
577 def regex_error_factory(description):
578 return lambda: print_error(description)
579
580
581 std_functions = [
582 ('malloc', 'Use xmalloc() in place of malloc()'),
583 ('calloc', 'Use xcalloc() in place of calloc()'),
584 ('realloc', 'Use xrealloc() in place of realloc()'),
585 ('strdup', 'Use xstrdup() in place of strdup()'),
586 ('asprintf', 'Use xasprintf() in place of asprintf()'),
587 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
588 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
589 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
590 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
591 ('strerror', 'Use ovs_strerror() in place of strerror()'),
592 ('sleep', 'Use xsleep() in place of sleep()'),
593 ('abort', 'Use ovs_abort() in place of abort()'),
594 ('assert', 'Use ovs_assert() in place of assert()'),
595 ('error', 'Use ovs_error() in place of error()'),
596 ]
597 checks += [
598 {'regex': r'(\.c|\.h)(\.in)?$',
599 'match_name': None,
600 'prereq': lambda x: not is_comment_line(x),
601 'check': regex_function_factory(function_name),
602 'print': regex_error_factory(description)}
603 for (function_name, description) in std_functions]
604
605
606 def regex_operator_factory(operator):
607 regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator)
608 return lambda x: regex.search(filter_comments(x)) is not None
609
610
611 infix_operators = \
612 [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=',
613 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
614 '&=', '^=', '|=', '<<=', '>>=']] \
615 + [r'[^<" ]<[^=" ]',
616 r'[^\->" ]>[^=" ]',
617 r'[^ !()/"]\*[^/]',
618 r'[^ !&()"]&',
619 r'[^" +(]\+[^"+;]',
620 r'[^" \-(]\-[^"\->;]',
621 r'[^" <>=!^|+\-*/%&]=[^"=]',
622 r'[^* ]/[^* ]']
623 checks += [
624 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
625 'prereq': lambda x: not is_comment_line(x),
626 'check': regex_operator_factory(operator),
627 'print': lambda: print_warning("Line lacks whitespace around operator")}
628 for operator in infix_operators]
629
630
631 def get_file_type_checks(filename):
632 """Returns the list of checks for a file based on matching the filename
633 against regex."""
634 global checks
635 checkList = []
636 for check in checks:
637 if check['regex'] is None and check['match_name'] is None:
638 checkList.append(check)
639 if check['regex'] is not None and \
640 re.compile(check['regex']).search(filename) is not None:
641 checkList.append(check)
642 elif check['match_name'] is not None and check['match_name'](filename):
643 checkList.append(check)
644 return checkList
645
646
647 def run_checks(current_file, line, lineno):
648 """Runs the various checks for the particular line. This will take
649 filename into account."""
650 global checking_file, total_line
651 print_line = False
652 for check in get_file_type_checks(current_file):
653 if 'prereq' in check and not check['prereq'](line):
654 continue
655 if check['check'](line):
656 if 'print' in check:
657 check['print']()
658 print_line = True
659
660 if print_line:
661 if checking_file:
662 print("%s:%d:" % (current_file, lineno))
663 else:
664 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
665 print("%s\n" % line)
666
667
668 def interim_line_check(current_file, line, lineno):
669 """Runs the various checks for the particular interim line. This will
670 take filename into account, and will check for the 'interim_line'
671 key before running the check."""
672 global checking_file, total_line
673 print_line = False
674 for check in get_file_type_checks(current_file):
675 if 'prereq' in check and not check['prereq'](line):
676 continue
677 if 'interim_line' in check and check['interim_line']:
678 if check['check'](line):
679 if 'print' in check:
680 check['print']()
681 print_line = True
682
683 if print_line:
684 if checking_file:
685 print("%s:%d:" % (current_file, lineno))
686 else:
687 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
688 print("%s\n" % line)
689
690
691 def run_file_checks(text):
692 """Runs the various checks for the text."""
693 for check in file_checks:
694 if check['regex'].search(text) is not None:
695 check['check'](text)
696
697
698 def ovs_checkpatch_parse(text, filename, author=None, committer=None):
699 global print_file_name, total_line, checking_file, \
700 empty_return_check_state
701
702 PARSE_STATE_HEADING = 0
703 PARSE_STATE_DIFF_HEADER = 1
704 PARSE_STATE_CHANGE_BODY = 2
705
706 lineno = 0
707 signatures = []
708 co_authors = []
709 parse = 0
710 current_file = filename if checking_file else ''
711 previous_file = ''
712 seppatch = re.compile(r'^---([\w]*| \S+)$')
713 hunks = re.compile(r'^(---|\+\+\+) (\S+)')
714 hunk_differences = re.compile(
715 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
716 is_author = re.compile(r'^(Author|From): (.*)$', re.I | re.M | re.S)
717 is_committer = re.compile(r'^(Commit: )(.*)$', re.I | re.M | re.S)
718 is_signature = re.compile(r'^(Signed-off-by: )(.*)$',
719 re.I | re.M | re.S)
720 is_co_author = re.compile(r'^(Co-authored-by: )(.*)$',
721 re.I | re.M | re.S)
722 is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$',
723 re.I | re.M | re.S)
724
725 reset_counters()
726
727 for line in text.split('\n'):
728 if current_file != previous_file:
729 previous_file = current_file
730
731 lineno = lineno + 1
732 total_line = total_line + 1
733 if len(line) <= 0:
734 continue
735
736 if checking_file:
737 parse = PARSE_STATE_CHANGE_BODY
738
739 if parse == PARSE_STATE_DIFF_HEADER:
740 match = hunks.match(line)
741 if match:
742 parse = PARSE_STATE_CHANGE_BODY
743 current_file = match.group(2)[2:]
744 print_file_name = current_file
745 continue
746 elif parse == PARSE_STATE_HEADING:
747 if seppatch.match(line):
748 parse = PARSE_STATE_DIFF_HEADER
749 if not skip_signoff_check:
750
751 # Check that the patch has an author, that the
752 # author is not among the co-authors, and that the
753 # co-authors are unique.
754 if not author:
755 print_error("Patch lacks author.")
756 continue
757 if " via " in author or "@openvswitch.org" in author:
758 print_error("Author should not be mailing list.")
759 continue
760 if author in co_authors:
761 print_error("Author should not be also be co-author.")
762 continue
763 if len(set(co_authors)) != len(co_authors):
764 print_error("Duplicate co-author.")
765
766 # Check that the author, all co-authors, and the
767 # committer (if any) signed off.
768 if author not in signatures:
769 print_error("Author %s needs to sign off." % author)
770 for ca in co_authors:
771 if ca not in signatures:
772 print_error("Co-author %s needs to sign off." % ca)
773 break
774 if (committer
775 and author != committer
776 and committer not in signatures):
777 print_error("Committer %s needs to sign off."
778 % committer)
779
780 # Check for signatures that we do not expect.
781 # This is only a warning because there can be,
782 # rarely, a signature chain.
783 #
784 # If we don't have a known committer, and there is
785 # a single extra sign-off, then do not warn
786 # because that extra sign-off is probably the
787 # committer.
788 extra_sigs = [x for x in signatures
789 if x not in co_authors
790 and x != author
791 and x != committer]
792 if len(extra_sigs) > 1 or (committer and extra_sigs):
793 print_warning("Unexpected sign-offs from developers "
794 "who are not authors or co-authors or "
795 "committers: %s"
796 % ", ".join(extra_sigs))
797 elif is_committer.match(line):
798 committer = is_committer.match(line).group(2)
799 elif is_author.match(line):
800 author = is_author.match(line).group(2)
801 elif is_signature.match(line):
802 m = is_signature.match(line)
803 signatures.append(m.group(2))
804 elif is_co_author.match(line):
805 m = is_co_author.match(line)
806 co_authors.append(m.group(2))
807 elif is_gerrit_change_id.match(line):
808 print_error(
809 "Remove Gerrit Change-Id's before submitting upstream.")
810 print("%d: %s\n" % (lineno, line))
811 elif parse == PARSE_STATE_CHANGE_BODY:
812 newfile = hunks.match(line)
813 if newfile:
814 current_file = newfile.group(2)[2:]
815 print_file_name = current_file
816 continue
817 reset_line_number = hunk_differences.match(line)
818 if reset_line_number:
819 empty_return_check_state = RETURN_CHECK_INITIAL_STATE
820 lineno = int(reset_line_number.group(3))
821 if lineno < 0:
822 lineno = -1 * lineno
823 lineno -= 1
824
825 if is_subtracted_line(line):
826 lineno -= 1
827 continue
828
829 cmp_line = added_line(line)
830
831 if not is_added_line(line):
832 interim_line_check(current_file, cmp_line, lineno)
833 continue
834
835 # Skip files which have /datapath in them, since they are
836 # linux or windows coding standards
837 if current_file.startswith('datapath'):
838 continue
839 if current_file.startswith('include/linux'):
840 continue
841 run_checks(current_file, cmp_line, lineno)
842
843 run_file_checks(text)
844 if __errors or __warnings:
845 return -1
846 return 0
847
848
849 def usage():
850 print("""\
851 Open vSwitch checkpatch.py
852 Checks a patch for trivial mistakes.
853 usage:
854 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
855
856 Input options:
857 -f|--check-file Arguments are source files, not patches.
858 -1, -2, ... Check recent commits in this repo.
859
860 Check options:
861 -h|--help This help message
862 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
863 -l|--skip-leading-whitespace Skips the leading whitespace test
864 -q|--quiet Only print error and warning information
865 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
866 -S|--spellcheck-comments Check C comments for possible spelling mistakes
867 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
868 % sys.argv[0])
869
870
871 def ovs_checkpatch_print_result(result):
872 global quiet, __warnings, __errors, total_line
873
874 if result < 0:
875 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
876 (total_line, __warnings, __errors))
877 elif not quiet:
878 print("Lines checked: %d, no obvious problems found\n" % (total_line))
879
880
881 def ovs_checkpatch_file(filename):
882 try:
883 mail = email.message_from_file(open(filename, 'r'))
884 except:
885 print_error("Unable to parse file '%s'. Is it a patch?" % filename)
886 return -1
887
888 for part in mail.walk():
889 if part.get_content_maintype() == 'multipart':
890 continue
891 result = ovs_checkpatch_parse(part.get_payload(decode=False), filename,
892 mail.get('Author', mail['From']),
893 mail['Commit'])
894 ovs_checkpatch_print_result(result)
895 return result
896
897
898 def partition(pred, iterable):
899 """Returns [[trues], [falses]], where [trues] is the items in
900 'iterable' that satisfy 'pred' and [falses] is all the rest."""
901 trues = []
902 falses = []
903 for item in iterable:
904 if pred(item):
905 trues.append(item)
906 else:
907 falses.append(item)
908 return trues, falses
909
910
911 if __name__ == '__main__':
912 try:
913 numeric_options, args = partition(lambda s: re.match('-[0-9]+$', s),
914 sys.argv[1:])
915 n_patches = int(numeric_options[-1][1:]) if numeric_options else 0
916
917 optlist, args = getopt.getopt(args, 'bhlstfSq',
918 ["check-file",
919 "help",
920 "skip-block-whitespace",
921 "skip-leading-whitespace",
922 "skip-signoff-lines",
923 "skip-trailing-whitespace",
924 "spellcheck-comments",
925 "quiet"])
926 except:
927 print("Unknown option encountered. Please rerun with -h for help.")
928 sys.exit(-1)
929
930 for o, a in optlist:
931 if o in ("-h", "--help"):
932 usage()
933 sys.exit(0)
934 elif o in ("-b", "--skip-block-whitespace"):
935 skip_block_whitespace_check = True
936 elif o in ("-l", "--skip-leading-whitespace"):
937 skip_leading_whitespace_check = True
938 elif o in ("-s", "--skip-signoff-lines"):
939 skip_signoff_check = True
940 elif o in ("-t", "--skip-trailing-whitespace"):
941 skip_trailing_whitespace_check = True
942 elif o in ("-f", "--check-file"):
943 checking_file = True
944 elif o in ("-S", "--spellcheck-comments"):
945 if not open_spell_check_dict():
946 print("WARNING: The enchant library isn't available.")
947 print(" Please install python enchant.")
948 else:
949 spellcheck_comments = True
950 elif o in ("-q", "--quiet"):
951 quiet = True
952 else:
953 print("Unknown option '%s'" % o)
954 sys.exit(-1)
955
956 if sys.stdout.isatty():
957 colors = True
958
959 if n_patches:
960 status = 0
961
962 git_log = 'git log --no-color --no-merges --pretty=format:"%H %s" '
963 with os.popen(git_log + '-%d' % n_patches, 'r') as f:
964 commits = f.read().split("\n")
965
966 for i in reversed(range(0, n_patches)):
967 revision, name = commits[i].split(" ", 1)
968 f = os.popen('''git format-patch -1 --stdout --pretty=format:"\
969 Author: %an <%ae>
970 Commit: %cn <%ce>
971 Subject: %s
972
973 %b" ''' + revision, 'r')
974 patch = f.read()
975 f.close()
976
977 if not quiet:
978 print('== Checking %s ("%s") ==' % (revision[0:12], name))
979 result = ovs_checkpatch_parse(patch, revision)
980 ovs_checkpatch_print_result(result)
981 if result:
982 status = -1
983 sys.exit(status)
984
985 if not args:
986 if sys.stdin.isatty():
987 usage()
988 sys.exit(-1)
989 result = ovs_checkpatch_parse(sys.stdin.read(), '-')
990 ovs_checkpatch_print_result(result)
991 sys.exit(result)
992
993 status = 0
994 for filename in args:
995 if not quiet:
996 print('== Checking "%s" ==' % filename)
997 result = ovs_checkpatch_file(filename)
998 if result:
999 status = -1
1000 sys.exit(status)