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