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