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