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