verifiers.py revision 11404:72b399971cbc
19983Sstever@gmail.com#!/usr/bin/env python 29983Sstever@gmail.com# 39983Sstever@gmail.com# Copyright (c) 2014, 2016 ARM Limited 49983Sstever@gmail.com# All rights reserved 59983Sstever@gmail.com# 69983Sstever@gmail.com# The license below extends only to copyright in the software and shall 79983Sstever@gmail.com# not be construed as granting a license to any other intellectual 89983Sstever@gmail.com# property including but not limited to intellectual property relating 99983Sstever@gmail.com# to a hardware implementation of the functionality of the software 109983Sstever@gmail.com# licensed hereunder. You may use the software subject to the license 119983Sstever@gmail.com# terms below provided that you ensure that this notice is replicated 129983Sstever@gmail.com# unmodified and in its entirety in all distributions of the software, 139983Sstever@gmail.com# modified or unmodified, in source code or in binary form. 149983Sstever@gmail.com# 159983Sstever@gmail.com# Copyright (c) 2006 The Regents of The University of Michigan 169983Sstever@gmail.com# Copyright (c) 2007,2011 The Hewlett-Packard Development Company 179983Sstever@gmail.com# Copyright (c) 2016 Advanced Micro Devices, Inc. 189983Sstever@gmail.com# All rights reserved. 199983Sstever@gmail.com# 209983Sstever@gmail.com# Redistribution and use in source and binary forms, with or without 219983Sstever@gmail.com# modification, are permitted provided that the following conditions are 229983Sstever@gmail.com# met: redistributions of source code must retain the above copyright 239983Sstever@gmail.com# notice, this list of conditions and the following disclaimer; 249983Sstever@gmail.com# redistributions in binary form must reproduce the above copyright 259983Sstever@gmail.com# notice, this list of conditions and the following disclaimer in the 269983Sstever@gmail.com# documentation and/or other materials provided with the distribution; 279983Sstever@gmail.com# neither the name of the copyright holders nor the names of its 289983Sstever@gmail.com# contributors may be used to endorse or promote products derived from 299983Sstever@gmail.com# this software without specific prior written permission. 309983Sstever@gmail.com# 319983Sstever@gmail.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 329983Sstever@gmail.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 339983Sstever@gmail.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 349983Sstever@gmail.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 359983Sstever@gmail.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 369983Sstever@gmail.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 379983Sstever@gmail.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 389983Sstever@gmail.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 399983Sstever@gmail.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 409983Sstever@gmail.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 419983Sstever@gmail.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 429983Sstever@gmail.com# 439983Sstever@gmail.com# Authors: Nathan Binkert 449983Sstever@gmail.com# Steve Reinhardt 459983Sstever@gmail.com# Andreas Sandberg 469983Sstever@gmail.com 479983Sstever@gmail.comfrom abc import ABCMeta, abstractmethod 489983Sstever@gmail.comfrom difflib import SequenceMatcher 499983Sstever@gmail.comimport inspect 509983Sstever@gmail.comimport os 519983Sstever@gmail.comimport re 529983Sstever@gmail.comimport sys 539983Sstever@gmail.com 549983Sstever@gmail.comimport style 559983Sstever@gmail.comimport sort_includes 569983Sstever@gmail.comfrom region import * 579983Sstever@gmail.comfrom file_types import lang_type 589983Sstever@gmail.com 599983Sstever@gmail.comdef _modified_regions(old, new): 609983Sstever@gmail.com m = SequenceMatcher(a=old, b=new, autojunk=False) 619983Sstever@gmail.com 629983Sstever@gmail.com regions = Regions() 639983Sstever@gmail.com for tag, i1, i2, j1, j2 in m.get_opcodes(): 649983Sstever@gmail.com if tag != "equal": 659983Sstever@gmail.com regions.extend(Region(i1, i2)) 669983Sstever@gmail.com return regions 679983Sstever@gmail.com 689983Sstever@gmail.com 699983Sstever@gmail.comclass Verifier(object): 709983Sstever@gmail.com """Base class for style verifiers 719983Sstever@gmail.com 729983Sstever@gmail.com Verifiers check for style violations and optionally fix such 739983Sstever@gmail.com violations. Implementations should either inherit from this class 749983Sstever@gmail.com (Verifier) if they need to work on entire files or LineVerifier if 759983Sstever@gmail.com they operate on a line-by-line basis. 769983Sstever@gmail.com 779983Sstever@gmail.com Subclasses must define these class attributes: 789983Sstever@gmail.com languages = set of strings identifying applicable languages 799983Sstever@gmail.com test_name = long descriptive name of test, will be used in 809983Sstever@gmail.com messages such as "error in <foo>" or "invalid <foo>" 819983Sstever@gmail.com opt_name = short name used to generate command-line options to 829983Sstever@gmail.com control the test (--fix-<foo>, --ignore-<foo>, etc.) 839983Sstever@gmail.com 849983Sstever@gmail.com """ 859983Sstever@gmail.com 869983Sstever@gmail.com __metaclass__ = ABCMeta 879983Sstever@gmail.com 889983Sstever@gmail.com def __init__(self, ui, opts, base=None): 899983Sstever@gmail.com self.ui = ui 909983Sstever@gmail.com self.base = base 919983Sstever@gmail.com 929983Sstever@gmail.com # opt_name must be defined as a class attribute of derived classes. 939983Sstever@gmail.com # Check test-specific opts first as these have precedence. 949983Sstever@gmail.com self.opt_fix = opts.get('fix_' + self.opt_name, False) 959983Sstever@gmail.com self.opt_ignore = opts.get('ignore_' + self.opt_name, False) 969983Sstever@gmail.com self.opt_skip = opts.get('skip_' + self.opt_name, False) 979983Sstever@gmail.com # If no test-specific opts were set, then set based on "-all" opts. 989983Sstever@gmail.com if not (self.opt_fix or self.opt_ignore or self.opt_skip): 999983Sstever@gmail.com self.opt_fix = opts.get('fix_all', False) 1009983Sstever@gmail.com self.opt_ignore = opts.get('ignore_all', False) 1019983Sstever@gmail.com self.opt_skip = opts.get('skip_all', False) 1029983Sstever@gmail.com 1039983Sstever@gmail.com def normalize_filename(self, name): 1049983Sstever@gmail.com abs_name = os.path.abspath(name) 1059983Sstever@gmail.com if self.base is None: 1069983Sstever@gmail.com return abs_name 1079983Sstever@gmail.com 1089983Sstever@gmail.com abs_base = os.path.abspath(self.base) 1099983Sstever@gmail.com return os.path.relpath(abs_name, start=abs_base) 1109983Sstever@gmail.com 1119983Sstever@gmail.com def open(self, filename, mode): 1129983Sstever@gmail.com try: 1139983Sstever@gmail.com f = file(filename, mode) 1149983Sstever@gmail.com except OSError, msg: 1159983Sstever@gmail.com print 'could not open file %s: %s' % (filename, msg) 1169983Sstever@gmail.com return None 1179983Sstever@gmail.com 1189983Sstever@gmail.com return f 1199983Sstever@gmail.com 1209983Sstever@gmail.com def skip(self, filename): 1219983Sstever@gmail.com # We never want to handle symlinks, so always skip them: If the location 1229983Sstever@gmail.com # pointed to is a directory, skip it. If the location is a file inside 1239983Sstever@gmail.com # the gem5 directory, it will be checked as a file, so symlink can be 1249983Sstever@gmail.com # skipped. If the location is a file outside gem5, we don't want to 1259983Sstever@gmail.com # check it anyway. 1269983Sstever@gmail.com if os.path.islink(filename): 1279983Sstever@gmail.com return True 1289983Sstever@gmail.com return lang_type(filename) not in self.languages 1299983Sstever@gmail.com 1309983Sstever@gmail.com def apply(self, filename, regions=all_regions): 1319983Sstever@gmail.com """Possibly apply to specified regions of file 'filename'. 1329983Sstever@gmail.com 1339983Sstever@gmail.com Verifier is skipped if --skip-<test> option was provided or if 1349983Sstever@gmail.com file is not of an applicable type. Otherwise file is checked 1359983Sstever@gmail.com and error messages printed. Errors are fixed or ignored if 1369983Sstever@gmail.com the corresponding --fix-<test> or --ignore-<test> options were 1379983Sstever@gmail.com provided. If neither, the user is prompted for an action. 1389983Sstever@gmail.com 1399983Sstever@gmail.com Returns True to abort, False otherwise. 1409983Sstever@gmail.com """ 1419983Sstever@gmail.com if not (self.opt_skip or self.skip(filename)): 1429983Sstever@gmail.com errors = self.check(filename, regions) 1439983Sstever@gmail.com if errors and not self.opt_ignore: 1449983Sstever@gmail.com if self.opt_fix: 1459983Sstever@gmail.com self.fix(filename, regions) 1469983Sstever@gmail.com else: 1479983Sstever@gmail.com result = self.ui.prompt("(a)bort, (i)gnore, or (f)ix?", 1489983Sstever@gmail.com 'aif', 'a') 1499983Sstever@gmail.com if result == 'f': 1509983Sstever@gmail.com self.fix(filename, regions) 1519983Sstever@gmail.com elif result == 'a': 1529983Sstever@gmail.com return True # abort 1539983Sstever@gmail.com 1549983Sstever@gmail.com return False 1559983Sstever@gmail.com 1569983Sstever@gmail.com @abstractmethod 1579983Sstever@gmail.com def check(self, filename, regions=all_regions): 1589983Sstever@gmail.com """Check specified regions of file 'filename'. 1599983Sstever@gmail.com 1609983Sstever@gmail.com Line-by-line checks can simply provide a check_line() method 1619983Sstever@gmail.com that returns True if the line is OK and False if it has an 1629983Sstever@gmail.com error. Verifiers that need a multi-line view (like 1639983Sstever@gmail.com SortedIncludes) must override this entire function. 1649983Sstever@gmail.com 1659983Sstever@gmail.com Returns a count of errors (0 if none), though actual non-zero 1669983Sstever@gmail.com count value is not currently used anywhere. 1679983Sstever@gmail.com """ 1689983Sstever@gmail.com pass 1699983Sstever@gmail.com 1709983Sstever@gmail.com @abstractmethod 1719983Sstever@gmail.com def fix(self, filename, regions=all_regions): 1729983Sstever@gmail.com """Fix specified regions of file 'filename'. 1739983Sstever@gmail.com 1749983Sstever@gmail.com Line-by-line fixes can simply provide a fix_line() method that 1759983Sstever@gmail.com returns the fixed line. Verifiers that need a multi-line view 1769983Sstever@gmail.com (like SortedIncludes) must override this entire function. 1779983Sstever@gmail.com """ 1789983Sstever@gmail.com pass 1799983Sstever@gmail.com 1809983Sstever@gmail.comclass LineVerifier(Verifier): 1819983Sstever@gmail.com def check(self, filename, regions=all_regions): 1829983Sstever@gmail.com f = self.open(filename, 'r') 1839983Sstever@gmail.com 1849983Sstever@gmail.com errors = 0 1859983Sstever@gmail.com for num,line in enumerate(f): 1869983Sstever@gmail.com if num not in regions: 1879983Sstever@gmail.com continue 1889983Sstever@gmail.com line = line.rstrip('\n') 1899983Sstever@gmail.com if not self.check_line(line): 1909983Sstever@gmail.com self.ui.write("invalid %s in %s:%d\n" % \ 1919983Sstever@gmail.com (self.test_name, filename, num + 1)) 1929983Sstever@gmail.com if self.ui.verbose: 1939983Sstever@gmail.com self.ui.write(">>%s<<\n" % line[:-1]) 1949983Sstever@gmail.com errors += 1 1959983Sstever@gmail.com return errors 1969983Sstever@gmail.com 1979983Sstever@gmail.com def fix(self, filename, regions=all_regions): 1989983Sstever@gmail.com f = self.open(filename, 'r+') 1999983Sstever@gmail.com 2009983Sstever@gmail.com lines = list(f) 2019983Sstever@gmail.com 2029983Sstever@gmail.com f.seek(0) 2039983Sstever@gmail.com f.truncate() 2049983Sstever@gmail.com 2059983Sstever@gmail.com for i,line in enumerate(lines): 2069983Sstever@gmail.com line = line.rstrip('\n') 2079983Sstever@gmail.com if i in regions: 2089983Sstever@gmail.com line = self.fix_line(line) 2099983Sstever@gmail.com 2109983Sstever@gmail.com f.write(line) 2119983Sstever@gmail.com f.write("\n") 2129983Sstever@gmail.com f.close() 2139983Sstever@gmail.com 2149983Sstever@gmail.com 2159983Sstever@gmail.com @abstractmethod 2169983Sstever@gmail.com def check_line(self, line): 2179983Sstever@gmail.com pass 2189983Sstever@gmail.com 2199983Sstever@gmail.com @abstractmethod 2209983Sstever@gmail.com def fix_line(self, line): 2219983Sstever@gmail.com pass 2229983Sstever@gmail.com 2239983Sstever@gmail.comclass Whitespace(LineVerifier): 2249983Sstever@gmail.com """Check whitespace. 2259983Sstever@gmail.com 2269983Sstever@gmail.com Specifically: 2279983Sstever@gmail.com - No tabs used for indent 2289983Sstever@gmail.com - No trailing whitespace 2299983Sstever@gmail.com """ 2309983Sstever@gmail.com 231 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons')) 232 test_name = 'whitespace' 233 opt_name = 'white' 234 235 _lead = re.compile(r'^([ \t]+)') 236 _trail = re.compile(r'([ \t]+)$') 237 238 def check_line(self, line): 239 match = Whitespace._lead.search(line) 240 if match and match.group(1).find('\t') != -1: 241 return False 242 243 match = Whitespace._trail.search(line) 244 if match: 245 return False 246 247 return True 248 249 def fix_line(self, line): 250 if Whitespace._lead.search(line): 251 newline = '' 252 for i,c in enumerate(line): 253 if c == ' ': 254 newline += ' ' 255 elif c == '\t': 256 newline += ' ' * (tabsize - len(newline) % tabsize) 257 else: 258 newline += line[i:] 259 break 260 261 line = newline 262 263 return line.rstrip() + '\n' 264 265 266class SortedIncludes(Verifier): 267 """Check for proper sorting of include statements""" 268 269 languages = sort_includes.default_languages 270 test_name = 'include file order' 271 opt_name = 'include' 272 273 def __init__(self, *args, **kwargs): 274 super(SortedIncludes, self).__init__(*args, **kwargs) 275 self.sort_includes = sort_includes.SortIncludes() 276 277 def check(self, filename, regions=all_regions): 278 f = self.open(filename, 'r') 279 norm_fname = self.normalize_filename(filename) 280 281 old = [ l.rstrip('\n') for l in f.xreadlines() ] 282 f.close() 283 284 if len(old) == 0: 285 return 0 286 287 language = lang_type(filename, old[0]) 288 new = list(self.sort_includes(old, norm_fname, language)) 289 290 modified = _modified_regions(old, new) & regions 291 292 if modified: 293 self.ui.write("invalid sorting of includes in %s\n" % (filename)) 294 if self.ui.verbose: 295 for start, end in modified.regions: 296 self.ui.write("bad region [%d, %d)\n" % (start, end)) 297 return 1 298 299 return 0 300 301 def fix(self, filename, regions=all_regions): 302 f = self.open(filename, 'r+') 303 304 old = f.readlines() 305 lines = [ l.rstrip('\n') for l in old ] 306 language = lang_type(filename, lines[0]) 307 sort_lines = list(self.sort_includes(lines, filename, language)) 308 new = ''.join(line + '\n' for line in sort_lines) 309 310 f.seek(0) 311 f.truncate() 312 313 for i,line in enumerate(sort_lines): 314 f.write(line) 315 f.write('\n') 316 f.close() 317 318 319class ControlSpace(LineVerifier): 320 """Check for exactly one space after if/while/for""" 321 322 languages = set(('C', 'C++')) 323 test_name = 'spacing after if/while/for' 324 opt_name = 'control' 325 326 _any_control = re.compile(r'\b(if|while|for)([ \t]*)\(') 327 328 def check_line(self, line): 329 match = ControlSpace._any_control.search(line) 330 return not (match and match.group(2) != " ") 331 332 def fix_line(self, line): 333 new_line = _any_control.sub(r'\1 (', line) 334 return new_line 335 336 337class LineLength(LineVerifier): 338 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons')) 339 test_name = 'line length' 340 opt_name = 'length' 341 342 def check_line(self, line): 343 return style.normalized_len(line) <= 78 344 345 def fix(self, filename, regions=all_regions): 346 self.ui.write("Warning: cannot automatically fix overly long lines.\n") 347 348 def fix_line(self, line): 349 pass 350 351class ControlCharacters(LineVerifier): 352 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons')) 353 test_name = 'control character' 354 opt_name = 'ascii' 355 356 valid = ('\n', '\t') 357 invalid = "".join([chr(i) for i in range(0, 0x20) if chr(i) not in valid]) 358 359 def check_line(self, line): 360 return self.fix_line(line) == line 361 362 def fix_line(self, line): 363 return line.translate(None, ControlCharacters.invalid) 364 365class BoolCompare(LineVerifier): 366 languages = set(('C', 'C++', 'python')) 367 test_name = 'boolean comparison' 368 opt_name = 'boolcomp' 369 370 regex = re.compile(r'\s*==\s*([Tt]rue|[Ff]alse)\b') 371 372 def check_line(self, line): 373 return self.regex.search(line) == None 374 375 def fix_line(self, line): 376 match = self.regex.search(line) 377 if match: 378 if match.group(1) in ('true', 'True'): 379 line = self.regex.sub('', line) 380 else: 381 self.ui.write("Warning: cannot automatically fix " 382 "comparisons with false/False.\n") 383 return line 384 385def is_verifier(cls): 386 """Determine if a class is a Verifier that can be instantiated""" 387 388 return inspect.isclass(cls) and issubclass(cls, Verifier) and \ 389 not inspect.isabstract(cls) 390 391# list of all verifier classes 392all_verifiers = [ v for n, v in \ 393 inspect.getmembers(sys.modules[__name__], is_verifier) ] 394