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 |