Deleted Added
sdiff udiff text old ( 11510:b539c1a6e597 ) new ( 11541:3d518944f0cc )
full compact
1#!/usr/bin/env python
2#
3# Copyright (c) 2014, 2016 ARM Limited
4# All rights reserved
5#
6# The license below extends only to copyright in the software and shall
7# not be construed as granting a license to any other intellectual
8# property including but not limited to intellectual property relating
9# to a hardware implementation of the functionality of the software
10# licensed hereunder. You may use the software subject to the license
11# terms below provided that you ensure that this notice is replicated
12# unmodified and in its entirety in all distributions of the software,
13# modified or unmodified, in source code or in binary form.
14#
15# Copyright (c) 2006 The Regents of The University of Michigan
16# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
17# Copyright (c) 2016 Advanced Micro Devices, Inc.
18# All rights reserved.
19#
20# Redistribution and use in source and binary forms, with or without
21# modification, are permitted provided that the following conditions are
22# met: redistributions of source code must retain the above copyright
23# notice, this list of conditions and the following disclaimer;
24# redistributions in binary form must reproduce the above copyright
25# notice, this list of conditions and the following disclaimer in the
26# documentation and/or other materials provided with the distribution;
27# neither the name of the copyright holders nor the names of its
28# contributors may be used to endorse or promote products derived from
29# this software without specific prior written permission.
30#
31# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42#
43# Authors: Nathan Binkert
44# Steve Reinhardt
45# Andreas Sandberg
46
47from abc import ABCMeta, abstractmethod
48from difflib import SequenceMatcher
49import inspect
50import os
51import re
52import sys
53
54import style
55import sort_includes
56from region import *
57from file_types import lang_type
58
59def _modified_regions(old, new):
60 try:
61 m = SequenceMatcher(a=old, b=new, autojunk=False)
62 except TypeError:
63 # autojunk was introduced in Python 2.7. We need a fallback
64 # mechanism to support old Python versions.
65 m = SequenceMatcher(a=old, b=new)
66 regions = Regions()
67 for tag, i1, i2, j1, j2 in m.get_opcodes():
68 if tag != "equal":
69 regions.extend(Region(i1, i2))
70 return regions
71
72
73class Verifier(object):
74 """Base class for style verifiers
75
76 Verifiers check for style violations and optionally fix such
77 violations. Implementations should either inherit from this class
78 (Verifier) if they need to work on entire files or LineVerifier if
79 they operate on a line-by-line basis.
80
81 Subclasses must define these class attributes:
82 languages = set of strings identifying applicable languages
83 test_name = long descriptive name of test, will be used in
84 messages such as "error in <foo>" or "invalid <foo>"
85 opt_name = short name used to generate command-line options to
86 control the test (--fix-<foo>, --ignore-<foo>, etc.)
87
88 """
89
90 __metaclass__ = ABCMeta
91
92 def __init__(self, ui, opts, base=None):
93 self.ui = ui
94 self.base = base
95
96 # opt_name must be defined as a class attribute of derived classes.
97 # Check test-specific opts first as these have precedence.
98 self.opt_fix = opts.get('fix_' + self.opt_name, False)
99 self.opt_ignore = opts.get('ignore_' + self.opt_name, False)
100 self.opt_skip = opts.get('skip_' + self.opt_name, False)
101 # If no test-specific opts were set, then set based on "-all" opts.
102 if not (self.opt_fix or self.opt_ignore or self.opt_skip):
103 self.opt_fix = opts.get('fix_all', False)
104 self.opt_ignore = opts.get('ignore_all', False)
105 self.opt_skip = opts.get('skip_all', False)
106
107 def normalize_filename(self, name):
108 abs_name = os.path.abspath(name)
109 if self.base is None:
110 return abs_name
111
112 abs_base = os.path.abspath(self.base)
113 return os.path.relpath(abs_name, start=abs_base)
114
115 def open(self, filename, mode):
116 try:
117 f = file(filename, mode)
118 except OSError, msg:
119 print 'could not open file %s: %s' % (filename, msg)
120 return None
121
122 return f
123
124 def skip(self, filename):
125 # We never want to handle symlinks, so always skip them: If the location
126 # pointed to is a directory, skip it. If the location is a file inside
127 # the gem5 directory, it will be checked as a file, so symlink can be
128 # skipped. If the location is a file outside gem5, we don't want to
129 # check it anyway.
130 if os.path.islink(filename):
131 return True
132 return lang_type(filename) not in self.languages
133
134 def apply(self, filename, regions=all_regions):
135 """Possibly apply to specified regions of file 'filename'.
136
137 Verifier is skipped if --skip-<test> option was provided or if
138 file is not of an applicable type. Otherwise file is checked
139 and error messages printed. Errors are fixed or ignored if
140 the corresponding --fix-<test> or --ignore-<test> options were
141 provided. If neither, the user is prompted for an action.
142
143 Returns True to abort, False otherwise.
144 """
145 if not (self.opt_skip or self.skip(filename)):
146 errors = self.check(filename, regions)
147 if errors and not self.opt_ignore:
148 if self.opt_fix:
149 self.fix(filename, regions)
150 else:
151 result = self.ui.prompt("(a)bort, (i)gnore, or (f)ix?",
152 'aif', 'a')
153 if result == 'f':
154 self.fix(filename, regions)
155 elif result == 'a':
156 return True # abort
157
158 return False
159
160 @abstractmethod
161 def check(self, filename, regions=all_regions):
162 """Check specified regions of file 'filename'.
163
164 Line-by-line checks can simply provide a check_line() method
165 that returns True if the line is OK and False if it has an
166 error. Verifiers that need a multi-line view (like
167 SortedIncludes) must override this entire function.
168
169 Returns a count of errors (0 if none), though actual non-zero
170 count value is not currently used anywhere.
171 """
172 pass
173
174 @abstractmethod
175 def fix(self, filename, regions=all_regions):
176 """Fix specified regions of file 'filename'.
177
178 Line-by-line fixes can simply provide a fix_line() method that
179 returns the fixed line. Verifiers that need a multi-line view
180 (like SortedIncludes) must override this entire function.
181 """
182 pass
183
184class LineVerifier(Verifier):
185 def check(self, filename, regions=all_regions):
186 f = self.open(filename, 'r')
187
188 lang = lang_type(filename)
189 assert lang in self.languages
190
191 errors = 0
192 for num,line in enumerate(f):
193 if num not in regions:
194 continue
195 line = line.rstrip('\n')
196 if not self.check_line(line, language=lang):
197 self.ui.write("invalid %s in %s:%d\n" % \
198 (self.test_name, filename, num + 1))
199 if self.ui.verbose:
200 self.ui.write(">>%s<<\n" % line[:-1])
201 errors += 1
202 f.close()
203 return errors
204
205 def fix(self, filename, regions=all_regions):
206 f = self.open(filename, 'r+')
207
208 lang = lang_type(filename)
209 assert lang in self.languages
210
211 lines = list(f)
212
213 f.seek(0)
214 f.truncate()
215
216 for i,line in enumerate(lines):
217 line = line.rstrip('\n')
218 if i in regions:
219 line = self.fix_line(line, language=lang)
220
221 f.write(line)
222 f.write("\n")
223 f.close()
224 self.current_language = None
225
226 @abstractmethod
227 def check_line(self, line, **kwargs):
228 pass
229
230 @abstractmethod
231 def fix_line(self, line, **kwargs):
232 pass
233
234class Whitespace(LineVerifier):
235 """Check whitespace.
236
237 Specifically:
238 - No tabs used for indent
239 - No trailing whitespace
240 """
241
242 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons',
243 'make', 'dts'))
244 trail_only = set(('make', 'dts'))
245
246 test_name = 'whitespace'
247 opt_name = 'white'
248
249 _lead = re.compile(r'^([ \t]+)')
250 _trail = re.compile(r'([ \t]+)$')
251
252
253 def skip_lead(self, language):
254 return language in Whitespace.trail_only
255
256 def check_line(self, line, language):
257 if not self.skip_lead(language):
258 match = Whitespace._lead.search(line)
259 if match and match.group(1).find('\t') != -1:
260 return False
261
262 match = Whitespace._trail.search(line)
263 if match:
264 return False
265
266 return True
267
268 def fix_line(self, line, language):
269 if not self.skip_lead(language) and Whitespace._lead.search(line):
270 newline = ''
271 for i,c in enumerate(line):
272 if c == ' ':
273 newline += ' '
274 elif c == '\t':
275 newline += ' ' * (tabsize - len(newline) % tabsize)
276 else:
277 newline += line[i:]
278 break
279
280 line = newline
281
282 return line.rstrip()
283
284
285class SortedIncludes(Verifier):
286 """Check for proper sorting of include statements"""
287
288 languages = sort_includes.default_languages
289 test_name = 'include file order'
290 opt_name = 'include'
291
292 def __init__(self, *args, **kwargs):
293 super(SortedIncludes, self).__init__(*args, **kwargs)
294 self.sort_includes = sort_includes.SortIncludes()
295
296 def check(self, filename, regions=all_regions):
297 f = self.open(filename, 'r')
298 norm_fname = self.normalize_filename(filename)
299
300 old = [ l.rstrip('\n') for l in f.xreadlines() ]
301 f.close()
302
303 if len(old) == 0:
304 return 0
305
306 language = lang_type(filename, old[0])
307 new = list(self.sort_includes(old, norm_fname, language))
308
309 modified = _modified_regions(old, new) & regions
310
311 if modified:
312 self.ui.write("invalid sorting of includes in %s\n" % (filename))
313 if self.ui.verbose:
314 for start, end in modified.regions:
315 self.ui.write("bad region [%d, %d)\n" % (start, end))
316 return 1
317
318 return 0
319
320 def fix(self, filename, regions=all_regions):
321 f = self.open(filename, 'r+')
322
323 old = f.readlines()
324 lines = [ l.rstrip('\n') for l in old ]
325 language = lang_type(filename, lines[0])
326 sort_lines = list(self.sort_includes(lines, filename, language))
327 new = ''.join(line + '\n' for line in sort_lines)
328
329 f.seek(0)
330 f.truncate()
331
332 for i,line in enumerate(sort_lines):
333 f.write(line)
334 f.write('\n')
335 f.close()
336
337
338class ControlSpace(LineVerifier):
339 """Check for exactly one space after if/while/for"""
340
341 languages = set(('C', 'C++'))
342 test_name = 'spacing after if/while/for'
343 opt_name = 'control'
344
345 _any_control = re.compile(r'\b(if|while|for)([ \t]*)\(')
346
347 def check_line(self, line, **kwargs):
348 match = ControlSpace._any_control.search(line)
349 return not (match and match.group(2) != " ")
350
351 def fix_line(self, line, **kwargs):
352 new_line = _any_control.sub(r'\1 (', line)
353 return new_line
354
355
356class LineLength(LineVerifier):
357 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
358 test_name = 'line length'
359 opt_name = 'length'
360
361 def check_line(self, line, **kwargs):
362 return style.normalized_len(line) <= 79
363
364 def fix(self, filename, regions=all_regions, **kwargs):
365 self.ui.write("Warning: cannot automatically fix overly long lines.\n")
366
367 def fix_line(self, line):
368 pass
369
370class ControlCharacters(LineVerifier):
371 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
372 test_name = 'control character'
373 opt_name = 'ascii'
374
375 valid = ('\n', '\t')
376 invalid = "".join([chr(i) for i in range(0, 0x20) if chr(i) not in valid])
377
378 def check_line(self, line, **kwargs):
379 return self.fix_line(line) == line
380
381 def fix_line(self, line, **kwargs):
382 return line.translate(None, ControlCharacters.invalid)
383
384class BoolCompare(LineVerifier):
385 languages = set(('C', 'C++', 'python'))
386 test_name = 'boolean comparison'
387 opt_name = 'boolcomp'
388
389 regex = re.compile(r'\s*==\s*([Tt]rue|[Ff]alse)\b')
390
391 def check_line(self, line, **kwargs):
392 return self.regex.search(line) == None
393
394 def fix_line(self, line, **kwargs):
395 match = self.regex.search(line)
396 if match:
397 if match.group(1) in ('true', 'True'):
398 line = self.regex.sub('', line)
399 else:
400 self.ui.write("Warning: cannot automatically fix "
401 "comparisons with false/False.\n")
402 return line
403
404def is_verifier(cls):
405 """Determine if a class is a Verifier that can be instantiated"""
406
407 return inspect.isclass(cls) and issubclass(cls, Verifier) and \
408 not inspect.isabstract(cls)
409
410# list of all verifier classes
411all_verifiers = [ v for n, v in \
412 inspect.getmembers(sys.modules[__name__], is_verifier) ]