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