]> git.proxmox.com Git - mirror_ovs.git/blame - utilities/checkpatch.py
ovs-vsctl: Add datapath_type column to show command.
[mirror_ovs.git] / utilities / checkpatch.py
CommitLineData
c599d5cc 1#!/usr/bin/env python
ebba2af6 2# Copyright (c) 2016, 2017 Red Hat, Inc.
4e99b70d 3# Copyright (c) 2018 Nicira, Inc.
c599d5cc
AC
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.
16from __future__ import print_function
17
18import email
19import getopt
a1fccabc 20import os
c599d5cc
AC
21import re
22import sys
23
a9e5ac0f
BS
24RETURN_CHECK_INITIAL_STATE = 0
25RETURN_CHECK_STATE_WITH_RETURN = 1
26RETURN_CHECK_AWAITING_BRACE = 2
c599d5cc
AC
27__errors = 0
28__warnings = 0
a9e5ac0f 29empty_return_check_state = 0
3239c793 30print_file_name = None
fb9410d8 31checking_file = False
ebba2af6
AC
32total_line = 0
33colors = False
999c7773 34spellcheck_comments = False
de8fa82a 35quiet = False
0fb02e8e
BP
36spell_check_dict = None
37
38
39def 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
3239c793
AC
96
97
ebba2af6
AC
98def get_color_end():
99 global colors
100 if colors:
101 return "\033[00m"
102 return ""
c599d5cc
AC
103
104
ebba2af6
AC
105def get_red_begin():
106 global colors
107 if colors:
108 return "\033[91m"
109 return ""
110
111
112def get_yellow_begin():
113 global colors
114 if colors:
115 return "\033[93m"
116 return ""
117
118
119def print_error(message):
c599d5cc 120 global __errors
ebba2af6 121 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message))
c599d5cc
AC
122
123 __errors = __errors + 1
124
125
ebba2af6 126def print_warning(message):
c599d5cc 127 global __warnings
ebba2af6 128 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message))
c599d5cc
AC
129
130 __warnings = __warnings + 1
131
132
81c2316f 133def reset_counters():
f61d40de 134 global __errors, __warnings, total_line
81c2316f
IM
135
136 __errors = 0
137 __warnings = 0
f61d40de 138 total_line = 0
81c2316f
IM
139
140
b537de13
BP
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 (...);".
862b9cce 145__parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_*EACH[_A-Z]*'
b537de13 146
c599d5cc 147__regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
4d7f5e51 148__regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
c599d5cc
AC
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]+$')
c61e93d6 152__regex_single_line_feed = re.compile(r'^\f$')
b537de13
BP
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)
a1193b4d 157__regex_for_if_parens_whitespace = \
b537de13 158 re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs)
30c7ffd5 159__regex_is_for_if_single_line_bracket = \
b537de13 160 re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs)
a1193b4d
AC
161__regex_ends_with_bracket = \
162 re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
d56ec3bc 163__regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]')
d6ec6c10 164__regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)')
4e99b70d 165__regex_has_comment = re.compile(r'.*(/\*|\*\s)')
b48aa143 166__regex_has_c99_comment = re.compile(r'.*//.*$')
12f62e9d 167__regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$')
3f9e248f
JS
168__regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$')
169__regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$')
4e99b70d 170__regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE)
8503a516
FL
171__regex_added_doc_rst = re.compile(
172 r'\ndiff .*Documentation/.*rst\nnew file mode')
a9e5ac0f 173__regex_empty_return = re.compile(r'\s*return;')
16770c6d
BS
174__regex_if_macros = re.compile(r'^ +(%s) \([\S][\s\S]+[\S]\) { \\' %
175 __parenthesized_constructs)
c599d5cc
AC
176
177skip_leading_whitespace_check = False
178skip_trailing_whitespace_check = False
179skip_block_whitespace_check = False
180skip_signoff_check = False
181
6982ee96
JS
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.
9aef43f0
BP
186line_length_blacklist = re.compile(
187 r'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules')
6982ee96 188
ae9c0985
BP
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.
9aef43f0 192leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules')
ae9c0985 193
c599d5cc 194
4d7f5e51
AC
195def 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
fb9410d8 199
c599d5cc
AC
200def is_added_line(line):
201 """Returns TRUE if the line in question is an added line.
202 """
fb9410d8
AC
203 global checking_file
204 return __regex_added_line.search(line) is not None or checking_file
205
206
207def 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
c599d5cc
AC
213
214
215def 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
c61e93d6
DDP
220 if (__regex_leading_with_whitespace_at_all.search(line) is not None and
221 __regex_single_line_feed.search(line) is None):
c599d5cc 222 return __regex_leading_with_spaces.search(line) is not None
c61e93d6 223
c599d5cc
AC
224 return True
225
226
227def trailing_whitespace_or_crlf(line):
228 """Returns TRUE if the trailing characters is whitespace
229 """
230 if skip_trailing_whitespace_check:
231 return False
c61e93d6
DDP
232 return (__regex_trailing_whitespace.search(line) is not None and
233 __regex_single_line_feed.search(line) is None)
c599d5cc
AC
234
235
236def 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
30c7ffd5
AC
248def 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:
973496b9 256 if letter == '(':
30c7ffd5 257 balance += 1
973496b9 258 elif letter == ')':
30c7ffd5 259 balance -= 1
973496b9 260 return balance == 0
30c7ffd5
AC
261
262 if __regex_is_for_if_single_line_bracket.search(line) is not None:
263 if not balanced_parens(line):
264 return True
16770c6d
BS
265
266 if __regex_ends_with_bracket.search(line) is None and \
267 __regex_if_macros.match(line) is None:
30c7ffd5 268 return False
3f9e248f
JS
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
30c7ffd5
AC
273 return True
274
275
6fc2799e
JS
276def 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
517f04ad
AC
282def line_length_check(line):
283 """Return TRUE if the line length is too long"""
284 if len(line) > 79:
860ffe70
BP
285 print_warning("Line is %d characters long (recommended limit is 79)"
286 % len(line))
517f04ad
AC
287 return True
288 return False
289
290
d6ec6c10
BP
291def 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
8df9a0c4 295
4e99b70d
JP
296def 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
d6ec6c10 300
8df9a0c4 301
b48aa143
IM
302def 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
12f62e9d
JS
307def 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
8df9a0c4 311
4e99b70d
JP
312def 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
12f62e9d 316
999c7773 317def filter_comments(current_line, keep=False):
8361e647
AC
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
999c7773
AC
329 if keep:
330 check_state = STATE_COMMENT_CONTENTS
331
8361e647
AC
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
999c7773 374def check_comment_spelling(line):
0fb02e8e 375 if not spell_check_dict or not spellcheck_comments:
999c7773
AC
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:
860ffe70
BP
401 print_warning("Check for spelling mistakes (e.g. \"%s\")"
402 % strword)
999c7773
AC
403 return True
404
405 return False
406
407
8503a516
FL
408def __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
438def __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
471def check_doc_docs_automake(text):
472 return __check_new_docs(text, 'automake')
473
474
475def check_new_docs_index(text):
476 return __check_new_docs(text, 'rst')
477
478
a9e5ac0f
BS
479def 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
8503a516
FL
506file_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
517f04ad
AC
513checks = [
514 {'regex': None,
09b94206 515 'match_name': lambda x: not line_length_blacklist.search(x),
860ffe70 516 'check': lambda x: line_length_check(x)},
907848bd 517
ae9c0985 518 {'regex': None,
09b94206 519 'match_name': lambda x: not leading_whitespace_blacklist.search(x),
907848bd 520 'check': lambda x: not leading_whitespace_is_spaces(x),
ebba2af6 521 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
907848bd
AC
522
523 {'regex': None, 'match_name': None,
524 'check': lambda x: trailing_whitespace_or_crlf(x),
ebba2af6 525 'print': lambda: print_warning("Line has trailing whitespace")},
907848bd 526
145a7e88 527 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
d6ec6c10 528 'prereq': lambda x: not is_comment_line(x),
907848bd 529 'check': lambda x: not if_and_for_whitespace_checks(x),
ebba2af6 530 'print': lambda: print_error("Improper whitespace around control block")},
907848bd 531
145a7e88 532 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
d6ec6c10 533 'prereq': lambda x: not is_comment_line(x),
907848bd 534 'check': lambda x: not if_and_for_end_with_bracket_check(x),
ebba2af6 535 'print': lambda: print_error("Inappropriate bracing around statement")},
907848bd 536
145a7e88 537 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
d6ec6c10 538 'prereq': lambda x: not is_comment_line(x),
907848bd
AC
539 'check': lambda x: pointer_whitespace_check(x),
540 'print':
12f62e9d
JS
541 lambda: print_error("Inappropriate spacing in pointer declaration")},
542
145a7e88 543 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
12f62e9d
JS
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")},
4e99b70d 548
145a7e88 549 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
4e99b70d
JP
550 'prereq': lambda x: has_comment(x),
551 'check': lambda x: has_xxx_mark(x),
552 'print': lambda: print_warning("Comment with 'xxx' marker")},
999c7773 553
b48aa143
IM
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
145a7e88 559 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
999c7773 560 'prereq': lambda x: has_comment(x),
860ffe70 561 'check': lambda x: check_comment_spelling(x)},
a9e5ac0f 562
145a7e88 563 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
a9e5ac0f
BS
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 },
517f04ad
AC
569]
570
571
b95d82bf 572def regex_function_factory(func_name):
6ecf961e 573 regex = re.compile(r'\b%s\([^)]*\)' % func_name)
b95d82bf
JS
574 return lambda x: regex.search(x) is not None
575
576
577def regex_error_factory(description):
578 return lambda: print_error(description)
579
580
581std_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()'),
d9d849fe 594 ('assert', 'Use ovs_assert() in place of assert()'),
b95d82bf
JS
595 ('error', 'Use ovs_error() in place of error()'),
596]
597checks += [
145a7e88 598 {'regex': r'(\.c|\.h)(\.in)?$',
b95d82bf 599 'match_name': None,
d6ec6c10 600 'prereq': lambda x: not is_comment_line(x),
b95d82bf
JS
601 'check': regex_function_factory(function_name),
602 'print': regex_error_factory(description)}
603 for (function_name, description) in std_functions]
604
605
0d7b16da
JS
606def regex_operator_factory(operator):
607 regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator)
8361e647 608 return lambda x: regex.search(filter_comments(x)) is not None
0d7b16da
JS
609
610
611infix_operators = \
bbb2cb20 612 [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=',
0d7b16da
JS
613 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
614 '&=', '^=', '|=', '<<=', '>>=']] \
9307fc46
IM
615 + [r'[^<" ]<[^=" ]',
616 r'[^\->" ]>[^=" ]',
617 r'[^ !()/"]\*[^/]',
618 r'[^ !&()"]&',
619 r'[^" +(]\+[^"+;]',
620 r'[^" \-(]\-[^"\->;]',
621 r'[^" <>=!^|+\-*/%&]=[^"=]',
622 r'[^* ]/[^* ]']
0d7b16da 623checks += [
145a7e88 624 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
0d7b16da
JS
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
517f04ad
AC
631def 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
647def run_checks(current_file, line, lineno):
648 """Runs the various checks for the particular line. This will take
649 filename into account."""
ebba2af6 650 global checking_file, total_line
a84a1edb 651 print_line = False
517f04ad 652 for check in get_file_type_checks(current_file):
d6ec6c10
BP
653 if 'prereq' in check and not check['prereq'](line):
654 continue
517f04ad 655 if check['check'](line):
860ffe70
BP
656 if 'print' in check:
657 check['print']()
a84a1edb
AC
658 print_line = True
659
660 if print_line:
ebba2af6
AC
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)
517f04ad
AC
666
667
a9e5ac0f
BS
668def 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
8503a516
FL
691def 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
3267343a 698def ovs_checkpatch_parse(text, filename, author=None, committer=None):
a9e5ac0f
BS
699 global print_file_name, total_line, checking_file, \
700 empty_return_check_state
6ed80686
AC
701
702 PARSE_STATE_HEADING = 0
703 PARSE_STATE_DIFF_HEADER = 1
704 PARSE_STATE_CHANGE_BODY = 2
705
c599d5cc
AC
706 lineno = 0
707 signatures = []
708 co_authors = []
709 parse = 0
95bd35d3 710 current_file = filename if checking_file else ''
6982ee96 711 previous_file = ''
128b46a0 712 seppatch = re.compile(r'^---([\w]*| \S+)$')
145a7e88 713 hunks = re.compile(r'^(---|\+\+\+) (\S+)')
4d7f5e51
AC
714 hunk_differences = re.compile(
715 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
3267343a
BP
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)
3ccb8899 718 is_signature = re.compile(r'^(Signed-off-by: )(.*)$',
c599d5cc 719 re.I | re.M | re.S)
3ccb8899 720 is_co_author = re.compile(r'^(Co-authored-by: )(.*)$',
c599d5cc 721 re.I | re.M | re.S)
bc03d850
IM
722 is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$',
723 re.I | re.M | re.S)
c599d5cc 724
81c2316f
IM
725 reset_counters()
726
822a3d88 727 for line in text.split('\n'):
6982ee96
JS
728 if current_file != previous_file:
729 previous_file = current_file
6982ee96 730
c599d5cc 731 lineno = lineno + 1
ebba2af6 732 total_line = total_line + 1
c599d5cc
AC
733 if len(line) <= 0:
734 continue
735
fb9410d8 736 if checking_file:
6ed80686 737 parse = PARSE_STATE_CHANGE_BODY
fb9410d8 738
6ed80686 739 if parse == PARSE_STATE_DIFF_HEADER:
c599d5cc
AC
740 match = hunks.match(line)
741 if match:
6ed80686 742 parse = PARSE_STATE_CHANGE_BODY
2797ff00 743 current_file = match.group(2)[2:]
3239c793 744 print_file_name = current_file
c599d5cc 745 continue
6ed80686 746 elif parse == PARSE_STATE_HEADING:
128b46a0 747 if seppatch.match(line):
6ed80686 748 parse = PARSE_STATE_DIFF_HEADER
c599d5cc 749 if not skip_signoff_check:
3267343a
BP
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
3bd2e465
BP
757 if " via " in author or "@openvswitch.org" in author:
758 print_error("Author should not be mailing list.")
759 continue
3267343a
BP
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)
c599d5cc
AC
801 elif is_signature.match(line):
802 m = is_signature.match(line)
3ccb8899 803 signatures.append(m.group(2))
c599d5cc
AC
804 elif is_co_author.match(line):
805 m = is_co_author.match(line)
3ccb8899 806 co_authors.append(m.group(2))
bc03d850
IM
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))
6ed80686 811 elif parse == PARSE_STATE_CHANGE_BODY:
c599d5cc
AC
812 newfile = hunks.match(line)
813 if newfile:
bbbe2fa2 814 current_file = newfile.group(2)[2:]
3239c793 815 print_file_name = current_file
c599d5cc 816 continue
4d7f5e51
AC
817 reset_line_number = hunk_differences.match(line)
818 if reset_line_number:
a9e5ac0f 819 empty_return_check_state = RETURN_CHECK_INITIAL_STATE
4d7f5e51
AC
820 lineno = int(reset_line_number.group(3))
821 if lineno < 0:
822 lineno = -1 * lineno
823 lineno -= 1
a9e5ac0f 824
4d7f5e51
AC
825 if is_subtracted_line(line):
826 lineno -= 1
c599d5cc 827 continue
fb9410d8
AC
828
829 cmp_line = added_line(line)
830
a9e5ac0f
BS
831 if not is_added_line(line):
832 interim_line_check(current_file, cmp_line, lineno)
833 continue
834
c599d5cc
AC
835 # Skip files which have /datapath in them, since they are
836 # linux or windows coding standards
2797ff00 837 if current_file.startswith('datapath'):
c599d5cc 838 continue
4f74db48
JS
839 if current_file.startswith('include/linux'):
840 continue
517f04ad 841 run_checks(current_file, cmp_line, lineno)
8503a516
FL
842
843 run_file_checks(text)
c599d5cc
AC
844 if __errors or __warnings:
845 return -1
846 return 0
847
848
849def usage():
a1fccabc
BP
850 print("""\
851Open vSwitch checkpatch.py
852Checks a patch for trivial mistakes.
853usage:
b90cfa86 854%s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
a1fccabc
BP
855
856Input options:
857-f|--check-file Arguments are source files, not patches.
858-1, -2, ... Check recent commits in this repo.
859
860Check 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
de8fa82a 864-q|--quiet Only print error and warning information
a1fccabc 865-s|--skip-signoff-lines Tolerate missing Signed-off-by line
999c7773 866-S|--spellcheck-comments Check C comments for possible spelling mistakes
a1fccabc
BP
867-t|--skip-trailing-whitespace Skips the trailing whitespace test"""
868 % sys.argv[0])
c599d5cc 869
a5e9c53c 870
81c2316f 871def ovs_checkpatch_print_result(result):
de8fa82a
AC
872 global quiet, __warnings, __errors, total_line
873
81c2316f
IM
874 if result < 0:
875 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
876 (total_line, __warnings, __errors))
de8fa82a 877 elif not quiet:
81c2316f
IM
878 print("Lines checked: %d, no obvious problems found\n" % (total_line))
879
880
c599d5cc
AC
881def 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
3267343a
BP
891 result = ovs_checkpatch_parse(part.get_payload(decode=False), filename,
892 mail.get('Author', mail['From']),
893 mail['Commit'])
81c2316f 894 ovs_checkpatch_print_result(result)
7d6b834f 895 return result
c599d5cc 896
884e0dfe 897
a1fccabc
BP
898def 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
c599d5cc
AC
911if __name__ == '__main__':
912 try:
a1fccabc
BP
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
de8fa82a 917 optlist, args = getopt.getopt(args, 'bhlstfSq',
fb9410d8
AC
918 ["check-file",
919 "help",
c599d5cc
AC
920 "skip-block-whitespace",
921 "skip-leading-whitespace",
922 "skip-signoff-lines",
999c7773 923 "skip-trailing-whitespace",
de8fa82a
AC
924 "spellcheck-comments",
925 "quiet"])
c599d5cc
AC
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
fb9410d8
AC
942 elif o in ("-f", "--check-file"):
943 checking_file = True
999c7773 944 elif o in ("-S", "--spellcheck-comments"):
0fb02e8e 945 if not open_spell_check_dict():
64b90b30 946 print("WARNING: The enchant library isn't available.")
999c7773
AC
947 print(" Please install python enchant.")
948 else:
949 spellcheck_comments = True
de8fa82a
AC
950 elif o in ("-q", "--quiet"):
951 quiet = True
c599d5cc
AC
952 else:
953 print("Unknown option '%s'" % o)
954 sys.exit(-1)
ebba2af6
AC
955
956 if sys.stdout.isatty():
957 colors = True
958
a1fccabc
BP
959 if n_patches:
960 status = 0
d962bad2
IM
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
057653ab 966 for i in reversed(range(0, n_patches)):
d962bad2 967 revision, name = commits[i].split(" ", 1)
3267343a
BP
968 f = os.popen('''git format-patch -1 --stdout --pretty=format:"\
969Author: %an <%ae>
970Commit: %cn <%ce>
971Subject: %s
972
973%b" ''' + revision, 'r')
a1fccabc
BP
974 patch = f.read()
975 f.close()
976
de8fa82a
AC
977 if not quiet:
978 print('== Checking %s ("%s") ==' % (revision[0:12], name))
81c2316f
IM
979 result = ovs_checkpatch_parse(patch, revision)
980 ovs_checkpatch_print_result(result)
981 if result:
a1fccabc
BP
982 status = -1
983 sys.exit(status)
984
b90cfa86 985 if not args:
c599d5cc
AC
986 if sys.stdin.isatty():
987 usage()
988 sys.exit(-1)
81c2316f
IM
989 result = ovs_checkpatch_parse(sys.stdin.read(), '-')
990 ovs_checkpatch_print_result(result)
991 sys.exit(result)
b90cfa86
IM
992
993 status = 0
994 for filename in args:
de8fa82a
AC
995 if not quiet:
996 print('== Checking "%s" ==' % filename)
b90cfa86
IM
997 result = ovs_checkpatch_file(filename)
998 if result:
999 status = -1
1000 sys.exit(status)