1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Checks C++ and Objective-C files for illegal includes.""" 6 7import codecs 8import os 9import re 10 11import results 12from rules import Rule, MessageRule 13 14 15class CppChecker(object): 16 17 EXTENSIONS = [ 18 '.h', 19 '.cc', 20 '.cpp', 21 '.m', 22 '.mm', 23 ] 24 25 # The maximum number of non-include lines we can see before giving up. 26 _MAX_UNINTERESTING_LINES = 50 27 28 # The maximum line length, this is to be efficient in the case of very long 29 # lines (which can't be #includes). 30 _MAX_LINE_LENGTH = 128 31 32 # This regular expression will be used to extract filenames from include 33 # statements. 34 _EXTRACT_INCLUDE_PATH = re.compile( 35 '[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"') 36 37 def __init__(self, verbose): 38 self._verbose = verbose 39 40 def CheckLine(self, rules, line, dependee_path, fail_on_temp_allow=False): 41 """Checks the given line with the given rule set. 42 43 Returns a tuple (is_include, dependency_violation) where 44 is_include is True only if the line is an #include or #import 45 statement, and dependency_violation is an instance of 46 results.DependencyViolation if the line violates a rule, or None 47 if it does not. 48 """ 49 found_item = self._EXTRACT_INCLUDE_PATH.match(line) 50 if not found_item: 51 return False, None # Not a match 52 53 include_path = found_item.group(1) 54 55 if '\\' in include_path: 56 return True, results.DependencyViolation( 57 include_path, 58 MessageRule('Include paths may not include backslashes.'), 59 rules) 60 61 if '/' not in include_path: 62 # Don't fail when no directory is specified. We may want to be more 63 # strict about this in the future. 64 if self._verbose: 65 print ' WARNING: directory specified with no path: ' + include_path 66 return True, None 67 68 rule = rules.RuleApplyingTo(include_path, dependee_path) 69 if (rule.allow == Rule.DISALLOW or 70 (fail_on_temp_allow and rule.allow == Rule.TEMP_ALLOW)): 71 return True, results.DependencyViolation(include_path, rule, rules) 72 return True, None 73 74 def CheckFile(self, rules, filepath): 75 if self._verbose: 76 print 'Checking: ' + filepath 77 78 dependee_status = results.DependeeStatus(filepath) 79 ret_val = '' # We'll collect the error messages in here 80 last_include = 0 81 with codecs.open(filepath, encoding='utf-8') as f: 82 in_if0 = 0 83 for line_num, line in enumerate(f): 84 if line_num - last_include > self._MAX_UNINTERESTING_LINES: 85 break 86 87 line = line.strip() 88 89 # Check to see if we're at / inside a #if 0 block 90 if line.startswith('#if 0'): 91 in_if0 += 1 92 continue 93 if in_if0 > 0: 94 if line.startswith('#if'): 95 in_if0 += 1 96 elif line.startswith('#endif'): 97 in_if0 -= 1 98 continue 99 100 is_include, violation = self.CheckLine(rules, line, filepath) 101 if is_include: 102 last_include = line_num 103 if violation: 104 dependee_status.AddViolation(violation) 105 106 return dependee_status 107 108 @staticmethod 109 def IsCppFile(file_path): 110 """Returns True iff the given path ends in one of the extensions 111 handled by this checker. 112 """ 113 return os.path.splitext(file_path)[1] in CppChecker.EXTENSIONS 114