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