]> git.proxmox.com Git - mirror_ovs.git/blame - utilities/checkpatch.py
ofproto: Add pipeline fields support for OF 1.5 packet-out
[mirror_ovs.git] / utilities / checkpatch.py
CommitLineData
c599d5cc 1#!/usr/bin/env python
ebba2af6 2# Copyright (c) 2016, 2017 Red Hat, Inc.
c599d5cc
AC
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at:
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15from __future__ import print_function
16
17import email
18import getopt
19import re
20import sys
21
22__errors = 0
23__warnings = 0
3239c793 24print_file_name = None
fb9410d8 25checking_file = False
ebba2af6
AC
26total_line = 0
27colors = False
3239c793
AC
28
29
ebba2af6
AC
30def get_color_end():
31 global colors
32 if colors:
33 return "\033[00m"
34 return ""
c599d5cc
AC
35
36
ebba2af6
AC
37def get_red_begin():
38 global colors
39 if colors:
40 return "\033[91m"
41 return ""
42
43
44def get_yellow_begin():
45 global colors
46 if colors:
47 return "\033[93m"
48 return ""
49
50
51def print_error(message):
c599d5cc 52 global __errors
ebba2af6 53 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message))
c599d5cc
AC
54
55 __errors = __errors + 1
56
57
ebba2af6 58def print_warning(message):
c599d5cc 59 global __warnings
ebba2af6 60 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message))
c599d5cc
AC
61
62 __warnings = __warnings + 1
63
64
b537de13
BP
65# These are keywords whose names are normally followed by a space and
66# something in parentheses (usually an expression) then a left curly brace.
67#
68# 'do' almost qualifies but it's also used as "do { ... } while (...);".
69__parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
70
c599d5cc 71__regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
4d7f5e51 72__regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
c599d5cc
AC
73__regex_leading_with_whitespace_at_all = re.compile(r'^\s+')
74__regex_leading_with_spaces = re.compile(r'^ +[\S]+')
75__regex_trailing_whitespace = re.compile(r'[^\S]+$')
c61e93d6 76__regex_single_line_feed = re.compile(r'^\f$')
b537de13
BP
77__regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]'
78 % __parenthesized_constructs)
79__regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]'
80 % __parenthesized_constructs)
a1193b4d 81__regex_for_if_parens_whitespace = \
b537de13 82 re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs)
30c7ffd5 83__regex_is_for_if_single_line_bracket = \
b537de13 84 re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs)
a1193b4d
AC
85__regex_ends_with_bracket = \
86 re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
d56ec3bc 87__regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]')
c599d5cc
AC
88
89skip_leading_whitespace_check = False
90skip_trailing_whitespace_check = False
91skip_block_whitespace_check = False
92skip_signoff_check = False
93
6982ee96
JS
94# Don't enforce character limit on files that include these characters in their
95# name, as they may have legitimate reasons to have longer lines.
96#
97# Python isn't checked as flake8 performs these checks during build.
98line_length_blacklist = ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
99 '.py']
100
c599d5cc 101
4d7f5e51
AC
102def is_subtracted_line(line):
103 """Returns TRUE if the line in question has been removed."""
104 return __regex_subtracted_line.search(line) is not None
105
fb9410d8 106
c599d5cc
AC
107def is_added_line(line):
108 """Returns TRUE if the line in question is an added line.
109 """
fb9410d8
AC
110 global checking_file
111 return __regex_added_line.search(line) is not None or checking_file
112
113
114def added_line(line):
115 """Returns the line formatted properly by removing diff syntax"""
116 global checking_file
117 if not checking_file:
118 return line[1:]
119 return line
c599d5cc
AC
120
121
122def leading_whitespace_is_spaces(line):
123 """Returns TRUE if the leading whitespace in added lines is spaces
124 """
125 if skip_leading_whitespace_check:
126 return True
c61e93d6
DDP
127 if (__regex_leading_with_whitespace_at_all.search(line) is not None and
128 __regex_single_line_feed.search(line) is None):
c599d5cc 129 return __regex_leading_with_spaces.search(line) is not None
c61e93d6 130
c599d5cc
AC
131 return True
132
133
134def trailing_whitespace_or_crlf(line):
135 """Returns TRUE if the trailing characters is whitespace
136 """
137 if skip_trailing_whitespace_check:
138 return False
c61e93d6
DDP
139 return (__regex_trailing_whitespace.search(line) is not None and
140 __regex_single_line_feed.search(line) is None)
c599d5cc
AC
141
142
143def if_and_for_whitespace_checks(line):
144 """Return TRUE if there is appropriate whitespace after if, for, while
145 """
146 if skip_block_whitespace_check:
147 return True
148 if (__regex_for_if_missing_whitespace.search(line) is not None or
149 __regex_for_if_too_much_whitespace.search(line) is not None or
150 __regex_for_if_parens_whitespace.search(line)):
151 return False
152 return True
153
154
30c7ffd5
AC
155def if_and_for_end_with_bracket_check(line):
156 """Return TRUE if there is not a bracket at the end of an if, for, while
157 block which fits on a single line ie: 'if (foo)'"""
158
159 def balanced_parens(line):
160 """This is a rather naive counter - it won't deal with quotes"""
161 balance = 0
162 for letter in line:
973496b9 163 if letter == '(':
30c7ffd5 164 balance += 1
973496b9 165 elif letter == ')':
30c7ffd5 166 balance -= 1
973496b9 167 return balance == 0
30c7ffd5
AC
168
169 if __regex_is_for_if_single_line_bracket.search(line) is not None:
170 if not balanced_parens(line):
171 return True
172 if __regex_ends_with_bracket.search(line) is None:
173 return False
174 return True
175
176
6fc2799e
JS
177def pointer_whitespace_check(line):
178 """Return TRUE if there is no space between a pointer name and the
179 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
180 return __regex_ptr_declaration_missing_whitespace.search(line) is not None
181
182
517f04ad
AC
183def line_length_check(line):
184 """Return TRUE if the line length is too long"""
185 if len(line) > 79:
186 return True
187 return False
188
189
190checks = [
191 {'regex': None,
192 'match_name':
193 lambda x: not any([fmt in x for fmt in line_length_blacklist]),
194 'check': lambda x: line_length_check(x),
ebba2af6 195 'print': lambda: print_warning("Line length is >79-characters long")},
907848bd
AC
196
197 {'regex': '$(?<!\.mk)',
198 'match_name': None,
199 'check': lambda x: not leading_whitespace_is_spaces(x),
ebba2af6 200 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
907848bd
AC
201
202 {'regex': None, 'match_name': None,
203 'check': lambda x: trailing_whitespace_or_crlf(x),
ebba2af6 204 'print': lambda: print_warning("Line has trailing whitespace")},
907848bd
AC
205
206 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
207 'check': lambda x: not if_and_for_whitespace_checks(x),
ebba2af6 208 'print': lambda: print_error("Improper whitespace around control block")},
907848bd
AC
209
210 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
211 'check': lambda x: not if_and_for_end_with_bracket_check(x),
ebba2af6 212 'print': lambda: print_error("Inappropriate bracing around statement")},
907848bd
AC
213
214 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
215 'check': lambda x: pointer_whitespace_check(x),
216 'print':
ebba2af6 217 lambda: print_error("Inappropriate spacing in pointer declaration")}
517f04ad
AC
218]
219
220
b95d82bf 221def regex_function_factory(func_name):
6ecf961e 222 regex = re.compile(r'\b%s\([^)]*\)' % func_name)
b95d82bf
JS
223 return lambda x: regex.search(x) is not None
224
225
226def regex_error_factory(description):
227 return lambda: print_error(description)
228
229
230std_functions = [
231 ('malloc', 'Use xmalloc() in place of malloc()'),
232 ('calloc', 'Use xcalloc() in place of calloc()'),
233 ('realloc', 'Use xrealloc() in place of realloc()'),
234 ('strdup', 'Use xstrdup() in place of strdup()'),
235 ('asprintf', 'Use xasprintf() in place of asprintf()'),
236 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
237 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
238 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
239 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
240 ('strerror', 'Use ovs_strerror() in place of strerror()'),
241 ('sleep', 'Use xsleep() in place of sleep()'),
242 ('abort', 'Use ovs_abort() in place of abort()'),
243 ('error', 'Use ovs_error() in place of error()'),
244]
245checks += [
246 {'regex': '(.c|.h)(.in)?$',
247 'match_name': None,
248 'check': regex_function_factory(function_name),
249 'print': regex_error_factory(description)}
250 for (function_name, description) in std_functions]
251
252
517f04ad
AC
253def get_file_type_checks(filename):
254 """Returns the list of checks for a file based on matching the filename
255 against regex."""
256 global checks
257 checkList = []
258 for check in checks:
259 if check['regex'] is None and check['match_name'] is None:
260 checkList.append(check)
261 if check['regex'] is not None and \
262 re.compile(check['regex']).search(filename) is not None:
263 checkList.append(check)
264 elif check['match_name'] is not None and check['match_name'](filename):
265 checkList.append(check)
266 return checkList
267
268
269def run_checks(current_file, line, lineno):
270 """Runs the various checks for the particular line. This will take
271 filename into account."""
ebba2af6 272 global checking_file, total_line
a84a1edb 273 print_line = False
517f04ad
AC
274 for check in get_file_type_checks(current_file):
275 if check['check'](line):
ebba2af6 276 check['print']()
a84a1edb
AC
277 print_line = True
278
279 if print_line:
ebba2af6
AC
280 if checking_file:
281 print("%s:%d:" % (current_file, lineno))
282 else:
283 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
284 print("%s\n" % line)
517f04ad
AC
285
286
95bd35d3
BP
287def ovs_checkpatch_parse(text, filename):
288 global print_file_name, total_line, checking_file
c599d5cc
AC
289 lineno = 0
290 signatures = []
291 co_authors = []
292 parse = 0
95bd35d3 293 current_file = filename if checking_file else ''
6982ee96 294 previous_file = ''
c599d5cc
AC
295 scissors = re.compile(r'^[\w]*---[\w]*')
296 hunks = re.compile('^(---|\+\+\+) (\S+)')
4d7f5e51
AC
297 hunk_differences = re.compile(
298 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
c599d5cc
AC
299 is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$',
300 re.I | re.M | re.S)
301 is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$',
302 re.I | re.M | re.S)
303
0b93d978 304 for line in text.decode(errors='ignore').split('\n'):
6982ee96
JS
305 if current_file != previous_file:
306 previous_file = current_file
6982ee96 307
c599d5cc 308 lineno = lineno + 1
ebba2af6 309 total_line = total_line + 1
c599d5cc
AC
310 if len(line) <= 0:
311 continue
312
fb9410d8
AC
313 if checking_file:
314 parse = 2
315
c599d5cc
AC
316 if parse == 1:
317 match = hunks.match(line)
318 if match:
319 parse = parse + 1
2797ff00 320 current_file = match.group(2)[2:]
3239c793 321 print_file_name = current_file
c599d5cc
AC
322 continue
323 elif parse == 0:
324 if scissors.match(line):
325 parse = parse + 1
326 if not skip_signoff_check:
327 if len(signatures) == 0:
328 print_error("No signatures found.")
83f76d4e 329 elif len(signatures) != 1 + len(co_authors):
c599d5cc
AC
330 print_error("Too many signoffs; "
331 "are you missing Co-authored-by lines?")
332 if not set(co_authors) <= set(signatures):
333 print_error("Co-authored-by/Signed-off-by corruption")
334 elif is_signature.match(line):
335 m = is_signature.match(line)
336 signatures.append(m.group(3))
337 elif is_co_author.match(line):
338 m = is_co_author.match(line)
339 co_authors.append(m.group(3))
340 elif parse == 2:
c599d5cc
AC
341 newfile = hunks.match(line)
342 if newfile:
bbbe2fa2 343 current_file = newfile.group(2)[2:]
3239c793 344 print_file_name = current_file
c599d5cc 345 continue
4d7f5e51
AC
346 reset_line_number = hunk_differences.match(line)
347 if reset_line_number:
348 lineno = int(reset_line_number.group(3))
349 if lineno < 0:
350 lineno = -1 * lineno
351 lineno -= 1
352 if is_subtracted_line(line):
353 lineno -= 1
c599d5cc
AC
354 if not is_added_line(line):
355 continue
fb9410d8
AC
356
357 cmp_line = added_line(line)
358
c599d5cc
AC
359 # Skip files which have /datapath in them, since they are
360 # linux or windows coding standards
2797ff00 361 if current_file.startswith('datapath'):
c599d5cc 362 continue
4f74db48
JS
363 if current_file.startswith('include/linux'):
364 continue
517f04ad 365 run_checks(current_file, cmp_line, lineno)
c599d5cc
AC
366 if __errors or __warnings:
367 return -1
368 return 0
369
370
371def usage():
372 print("Open vSwitch checkpatch.py")
373 print("Checks a patch for trivial mistakes.")
374 print("usage:")
375 print("%s [options] [patch file]" % sys.argv[0])
376 print("options:")
377 print("-h|--help\t\t\t\tThis help message")
378 print("-b|--skip-block-whitespace\t"
379 "Skips the if/while/for whitespace tests")
fb9410d8 380 print("-f|--check-file\t\t\tCheck a file instead of a patchfile.")
c599d5cc
AC
381 print("-l|--skip-leading-whitespace\t"
382 "Skips the leading whitespace test")
383 print("-s|--skip-signoff-lines\t"
384 "Do not emit an error if no Signed-off-by line is present")
385 print("-t|--skip-trailing-whitespace\t"
386 "Skips the trailing whitespace test")
387
a5e9c53c 388
c599d5cc 389def ovs_checkpatch_file(filename):
ebba2af6 390 global __warnings, __errors, checking_file, total_line
c599d5cc
AC
391 try:
392 mail = email.message_from_file(open(filename, 'r'))
393 except:
394 print_error("Unable to parse file '%s'. Is it a patch?" % filename)
395 return -1
396
397 for part in mail.walk():
398 if part.get_content_maintype() == 'multipart':
399 continue
95bd35d3 400 result = ovs_checkpatch_parse(part.get_payload(decode=True), filename)
7d6b834f 401 if result < 0:
ebba2af6
AC
402 print("Lines checked: %d, Warnings: %d, Errors: %d" %
403 (total_line, __warnings, __errors))
404 else:
405 print("Lines checked: %d, no obvious problems found" % (total_line))
7d6b834f 406 return result
c599d5cc 407
884e0dfe 408
c599d5cc
AC
409if __name__ == '__main__':
410 try:
fb9410d8
AC
411 optlist, args = getopt.getopt(sys.argv[1:], 'bhlstf',
412 ["check-file",
413 "help",
c599d5cc
AC
414 "skip-block-whitespace",
415 "skip-leading-whitespace",
416 "skip-signoff-lines",
417 "skip-trailing-whitespace"])
418 except:
419 print("Unknown option encountered. Please rerun with -h for help.")
420 sys.exit(-1)
421
422 for o, a in optlist:
423 if o in ("-h", "--help"):
424 usage()
425 sys.exit(0)
426 elif o in ("-b", "--skip-block-whitespace"):
427 skip_block_whitespace_check = True
428 elif o in ("-l", "--skip-leading-whitespace"):
429 skip_leading_whitespace_check = True
430 elif o in ("-s", "--skip-signoff-lines"):
431 skip_signoff_check = True
432 elif o in ("-t", "--skip-trailing-whitespace"):
433 skip_trailing_whitespace_check = True
fb9410d8
AC
434 elif o in ("-f", "--check-file"):
435 checking_file = True
c599d5cc
AC
436 else:
437 print("Unknown option '%s'" % o)
438 sys.exit(-1)
ebba2af6
AC
439
440 if sys.stdout.isatty():
441 colors = True
442
c599d5cc
AC
443 try:
444 filename = args[0]
445 except:
446 if sys.stdin.isatty():
447 usage()
448 sys.exit(-1)
95bd35d3 449 sys.exit(ovs_checkpatch_parse(sys.stdin.read()), '-')
c599d5cc 450 sys.exit(ovs_checkpatch_file(filename))