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 m = SequenceMatcher(a=old, b=new, autojunk=False) 61 62 regions = Regions() 63 for tag, i1, i2, j1, j2 in m.get_opcodes(): 64 if tag != "equal": 65 regions.extend(Region(i1, i2)) 66 return regions 67 68 69class Verifier(object): 70 """Base class for style verifiers 71 72 Verifiers check for style violations and optionally fix such 73 violations. Implementations should either inherit from this class 74 (Verifier) if they need to work on entire files or LineVerifier if 75 they operate on a line-by-line basis. 76 77 Subclasses must define these class attributes: 78 languages = set of strings identifying applicable languages 79 test_name = long descriptive name of test, will be used in 80 messages such as "error in <foo>" or "invalid <foo>" 81 opt_name = short name used to generate command-line options to 82 control the test (--fix-<foo>, --ignore-<foo>, etc.) 83 84 """ 85 86 __metaclass__ = ABCMeta 87 88 def __init__(self, ui, opts, base=None): 89 self.ui = ui 90 self.base = base 91 92 # opt_name must be defined as a class attribute of derived classes. 93 # Check test-specific opts first as these have precedence. 94 self.opt_fix = opts.get('fix_' + self.opt_name, False) 95 self.opt_ignore = opts.get('ignore_' + self.opt_name, False) 96 self.opt_skip = opts.get('skip_' + self.opt_name, False) 97 # If no test-specific opts were set, then set based on "-all" opts. 98 if not (self.opt_fix or self.opt_ignore or self.opt_skip): 99 self.opt_fix = opts.get('fix_all', False) 100 self.opt_ignore = opts.get('ignore_all', False) 101 self.opt_skip = opts.get('skip_all', False) 102 103 def normalize_filename(self, name): 104 abs_name = os.path.abspath(name) 105 if self.base is None: 106 return abs_name 107 108 abs_base = os.path.abspath(self.base) 109 return os.path.relpath(abs_name, start=abs_base) 110 111 def open(self, filename, mode): 112 try: 113 f = file(filename, mode) 114 except OSError, msg: 115 print 'could not open file %s: %s' % (filename, msg) 116 return None 117 118 return f 119 120 def skip(self, filename): 121 # We never want to handle symlinks, so always skip them: If the location 122 # pointed to is a directory, skip it. If the location is a file inside 123 # the gem5 directory, it will be checked as a file, so symlink can be 124 # skipped. If the location is a file outside gem5, we don't want to 125 # check it anyway. 126 if os.path.islink(filename): 127 return True 128 return lang_type(filename) not in self.languages 129 130 def apply(self, filename, regions=all_regions): 131 """Possibly apply to specified regions of file 'filename'. 132 133 Verifier is skipped if --skip-<test> option was provided or if 134 file is not of an applicable type. Otherwise file is checked 135 and error messages printed. Errors are fixed or ignored if 136 the corresponding --fix-<test> or --ignore-<test> options were 137 provided. If neither, the user is prompted for an action. 138 139 Returns True to abort, False otherwise. 140 """ 141 if not (self.opt_skip or self.skip(filename)): 142 errors = self.check(filename, regions) 143 if errors and not self.opt_ignore: 144 if self.opt_fix: 145 self.fix(filename, regions) 146 else: 147 result = self.ui.prompt("(a)bort, (i)gnore, or (f)ix?", 148 'aif', 'a') 149 if result == 'f': 150 self.fix(filename, regions) 151 elif result == 'a': 152 return True # abort 153 154 return False 155 156 @abstractmethod 157 def check(self, filename, regions=all_regions): 158 """Check specified regions of file 'filename'. 159 160 Line-by-line checks can simply provide a check_line() method 161 that returns True if the line is OK and False if it has an 162 error. Verifiers that need a multi-line view (like 163 SortedIncludes) must override this entire function. 164 165 Returns a count of errors (0 if none), though actual non-zero 166 count value is not currently used anywhere. 167 """ 168 pass 169 170 @abstractmethod 171 def fix(self, filename, regions=all_regions): 172 """Fix specified regions of file 'filename'. 173 174 Line-by-line fixes can simply provide a fix_line() method that 175 returns the fixed line. Verifiers that need a multi-line view 176 (like SortedIncludes) must override this entire function. 177 """ 178 pass 179 180class LineVerifier(Verifier): 181 def check(self, filename, regions=all_regions): 182 f = self.open(filename, 'r') 183 184 errors = 0 185 for num,line in enumerate(f): 186 if num not in regions: 187 continue 188 line = line.rstrip('\n') 189 if not self.check_line(line): 190 self.ui.write("invalid %s in %s:%d\n" % \ 191 (self.test_name, filename, num + 1)) 192 if self.ui.verbose: 193 self.ui.write(">>%s<<\n" % line[:-1]) 194 errors += 1 195 return errors 196 197 def fix(self, filename, regions=all_regions): 198 f = self.open(filename, 'r+') 199 200 lines = list(f) 201 202 f.seek(0) 203 f.truncate() 204 205 for i,line in enumerate(lines): 206 line = line.rstrip('\n') 207 if i in regions: 208 line = self.fix_line(line) 209 210 f.write(line) 211 f.write("\n") 212 f.close() 213 214 215 @abstractmethod 216 def check_line(self, line): 217 pass 218 219 @abstractmethod 220 def fix_line(self, line): 221 pass 222 223class Whitespace(LineVerifier): 224 """Check whitespace. 225 226 Specifically: 227 - No tabs used for indent 228 - No trailing whitespace 229 """ 230 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):
|
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) ]
|