OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # suppressions.py | 6 # suppressions.py |
7 | 7 |
8 """Post-process Valgrind suppression matcher. | 8 """Post-process Valgrind suppression matcher. |
9 | 9 |
10 Suppressions are defined as follows: | 10 Suppressions are defined as follows: |
(...skipping 21 matching lines...) Expand all Loading... | |
32 | 32 |
33 class Suppression(object): | 33 class Suppression(object): |
34 """This class represents a single stack trace suppression. | 34 """This class represents a single stack trace suppression. |
35 | 35 |
36 Attributes: | 36 Attributes: |
37 description: A string representing the error description. | 37 description: A string representing the error description. |
38 type: A string representing the error type, e.g. Memcheck:Leak. | 38 type: A string representing the error type, e.g. Memcheck:Leak. |
39 stack: a list of "fun:" or "obj:" or ellipsis lines. | 39 stack: a list of "fun:" or "obj:" or ellipsis lines. |
40 """ | 40 """ |
41 | 41 |
42 def __init__(self, description, type, stack): | 42 def __init__(self, description, type, stack, defined_at): |
43 """Inits Suppression. | 43 """Inits Suppression. |
44 | 44 |
45 Args: Same as class attributes. | 45 description, type, stack: same as class attributes |
46 defined_at: file:line identifying where the suppression was defined | |
46 """ | 47 """ |
47 self.description = description | 48 self.description = description |
48 self.type = type | 49 self.type = type |
49 self._stack = stack | 50 self._stack = stack |
51 self.defined_at = defined_at | |
50 re_line = '{\n.*\n%s\n' % self.type | 52 re_line = '{\n.*\n%s\n' % self.type |
51 re_bucket = '' | 53 re_bucket = '' |
52 for line in stack: | 54 for line in stack: |
53 if line == ELLIPSIS: | 55 if line == ELLIPSIS: |
54 re_line += re.escape(re_bucket) | 56 re_line += re.escape(re_bucket) |
55 re_bucket = '' | 57 re_bucket = '' |
56 re_line += '(.*\n)*' | 58 re_line += '(.*\n)*' |
57 else: | 59 else: |
58 for char in line: | 60 for char in line: |
59 if char == '*': | 61 if char == '*': |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
120 if not self._stack: | 122 if not self._stack: |
121 return False | 123 return False |
122 lines = [f.strip() for f in suppression_from_report] | 124 lines = [f.strip() for f in suppression_from_report] |
123 if self._re.match('\n'.join(lines) + '\n'): | 125 if self._re.match('\n'.join(lines) + '\n'): |
124 return True | 126 return True |
125 else: | 127 else: |
126 return False | 128 return False |
127 | 129 |
128 | 130 |
129 class SuppressionError(Exception): | 131 class SuppressionError(Exception): |
130 def __init__(self, filename, line, message=''): | 132 def __init__(self, message, happened_at): |
131 Exception.__init__(self, filename, line, message) | |
132 self._file = filename | |
133 self._line = line | |
134 self._message = message | 133 self._message = message |
134 self._happened_at = happened_at | |
135 | 135 |
136 def __str__(self): | 136 def __str__(self): |
137 return 'Error reading suppressions from "%s" (line %d): %s.' % ( | 137 return 'Error reading suppressions at %s - %s' % ( |
138 self._file, self._line, self._message) | 138 self._happened_at, self._message) |
139 | 139 |
140 def ReadSuppressionsFromFile(filename): | 140 def ReadSuppressionsFromFile(filename): |
141 """Read suppressions from the given file and return them as a list""" | 141 """Read suppressions from the given file and return them as a list""" |
142 input_file = file(filename, 'r') | 142 input_file = file(filename, 'r') |
143 try: | 143 try: |
144 return ReadSuppressions(input_file, filename) | 144 return ReadSuppressions(input_file, filename) |
145 except SuppressionError: | 145 except SuppressionError: |
146 input_file.close() | 146 input_file.close() |
147 raise | 147 raise |
148 | 148 |
149 def ReadSuppressions(lines, supp_descriptor): | 149 def ReadSuppressions(lines, supp_descriptor): |
150 """Given a list of lines, returns a list of suppressions. | 150 """Given a list of lines, returns a list of suppressions. |
151 | 151 |
152 Args: | 152 Args: |
153 lines: a list of lines containing suppressions. | 153 lines: a list of lines containing suppressions. |
154 supp_descriptor: should typically be a filename. | 154 supp_descriptor: should typically be a filename. |
155 Used only when parsing errors happen. | 155 Used only when printing errors. |
156 """ | 156 """ |
157 result = [] | 157 result = [] |
158 cur_descr = '' | 158 cur_descr = '' |
159 cur_type = '' | 159 cur_type = '' |
160 cur_stack = [] | 160 cur_stack = [] |
161 in_suppression = False | 161 in_suppression = False |
162 nline = 0 | 162 nline = 0 |
163 for line in lines: | 163 for line in lines: |
164 nline += 1 | 164 nline += 1 |
165 line = line.strip() | 165 line = line.strip() |
166 if line.startswith('#'): | 166 if line.startswith('#'): |
167 continue | 167 continue |
168 if not in_suppression: | 168 if not in_suppression: |
169 if not line: | 169 if not line: |
170 # empty lines between suppressions | 170 # empty lines between suppressions |
171 pass | 171 pass |
172 elif line.startswith('{'): | 172 elif line.startswith('{'): |
173 in_suppression = True | 173 in_suppression = True |
174 pass | 174 pass |
175 else: | 175 else: |
176 raise SuppressionError(supp_descriptor, nline, | 176 raise SuppressionError('Expected: "{"', |
177 'Expected: "{"') | 177 "%s:%d" % (supp_descriptor, nline)) |
178 elif line.startswith('}'): | 178 elif line.startswith('}'): |
179 result.append(Suppression(cur_descr, cur_type, cur_stack)) | 179 result.append( |
180 Suppression(cur_descr, cur_type, cur_stack, | |
181 "%s:%d" % (supp_descriptor, nline))) | |
180 cur_descr = '' | 182 cur_descr = '' |
181 cur_type = '' | 183 cur_type = '' |
182 cur_stack = [] | 184 cur_stack = [] |
183 in_suppression = False | 185 in_suppression = False |
184 elif not cur_descr: | 186 elif not cur_descr: |
185 cur_descr = line | 187 cur_descr = line |
186 continue | 188 continue |
187 elif not cur_type: | 189 elif not cur_type: |
188 if (not line.startswith("Memcheck:")) and \ | 190 if (not line.startswith("Memcheck:")) and \ |
189 (not line.startswith("ThreadSanitizer:")) and \ | 191 (not line.startswith("ThreadSanitizer:")) and \ |
190 (line != "Heapcheck:Leak"): | 192 (line != "Heapcheck:Leak"): |
191 raise SuppressionError(supp_descriptor, nline, | 193 raise SuppressionError( |
192 '"Memcheck:TYPE" , "ThreadSanitizer:TYPE" or "Heapcheck:Leak ' | 194 'Expected "Memcheck:TYPE", "ThreadSanitizer:TYPE" ' |
193 'is expected, got "%s"' % line) | 195 'or "Heapcheck:Leak", got "%s"' % line, |
196 "%s:%d" % (supp_descriptor, nline)) | |
194 supp_type = line.split(':')[1] | 197 supp_type = line.split(':')[1] |
195 if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8", | 198 if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8", |
196 "Cond", "Free", "Jump", "Leak", "Overlap", "Param", | 199 "Cond", "Free", "Jump", "Leak", "Overlap", "Param", |
197 "Value1", "Value2", "Value4", "Value8", | 200 "Value1", "Value2", "Value4", "Value8", |
198 "Race", "UnlockNonLocked", "InvalidLock", | 201 "Race", "UnlockNonLocked", "InvalidLock", |
199 "Unaddressable", "Uninitialized"]: | 202 "Unaddressable", "Uninitialized"]: |
200 raise SuppressionError(supp_descriptor, nline, | 203 raise SuppressionError('Unknown suppression type "%s"' % supp_type, |
201 'Unknown suppression type "%s"' % supp_type) | 204 "%s:%d" % (supp_descriptor, nline)) |
202 cur_type = line | 205 cur_type = line |
203 continue | 206 continue |
204 elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line): | 207 elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line): |
205 cur_stack.append(line.strip()) | 208 cur_stack.append(line.strip()) |
206 elif len(cur_stack) == 0 and cur_type == "Memcheck:Param": | 209 elif len(cur_stack) == 0 and cur_type == "Memcheck:Param": |
207 cur_stack.append(line.strip()) | 210 cur_stack.append(line.strip()) |
208 else: | 211 else: |
209 raise SuppressionError(supp_descriptor, nline, | 212 raise SuppressionError( |
210 '"fun:function_name" or "obj:object_file" ' \ | 213 '"fun:function_name" or "obj:object_file" or "..." expected', |
211 'or "..." expected') | 214 "%s:%d" % (supp_descriptor, nline)) |
212 return result | 215 return result |
213 | 216 |
214 | 217 |
218 def PresubmitCheck(filenames): | |
219 """A helper function useful in PRESUBMIT.py | |
220 Returns a list of errors or []. | |
221 """ | |
222 ret = [] | |
223 | |
224 # TODO(timurrrr): warn on putting suppressions into a wrong file, | |
225 # e.g. TSan suppression in a memcheck file. | |
226 | |
227 for f in filenames: | |
228 try: | |
229 known_supp_names = {} # Key: name, Value: suppression. | |
230 supps = ReadSuppressionsFromFile(f) | |
231 for s in supps: | |
232 if re.search("<.*suppression.name.here>", s.description): | |
233 # Suppression name line is | |
234 # <insert_a_suppression_name_here> for Memcheck, | |
235 # <Put your suppression name here> for TSan, | |
236 # name=<insert_a_suppression_name_here> for DrMemory | |
237 ret.append( | |
238 SuppressionError( | |
239 "You've forgotten to put a suppression name like bug_XXX", | |
240 s.defined_at)) | |
241 continue | |
242 | |
243 if s.description in known_supp_names: | |
244 ret.append( | |
245 SuppressionError( | |
246 'Suppression named "%s" is defined more than once, ' | |
247 'see %s' % (s.description, | |
248 known_supp_names[s.description].defined_at), | |
Alexander Potapenko
2011/12/15 13:07:23
pls fix the indentation
Timur Iskhodzhanov
2011/12/15 13:17:27
Done.
| |
249 s.defined_at)) | |
250 else: | |
251 known_supp_names[s.description] = s | |
252 | |
253 except SuppressionError as e: | |
254 ret.append(e) | |
255 return ret | |
256 | |
257 | |
215 def TestStack(stack, positive, negative): | 258 def TestStack(stack, positive, negative): |
216 """A helper function for SelfTest() that checks a single stack. | 259 """A helper function for SelfTest() that checks a single stack. |
217 | 260 |
218 Args: | 261 Args: |
219 stack: the stack to match the suppressions. | 262 stack: the stack to match the suppressions. |
220 positive: the list of suppressions that must match the given stack. | 263 positive: the list of suppressions that must match the given stack. |
221 negative: the list of suppressions that should not match. | 264 negative: the list of suppressions that should not match. |
222 """ | 265 """ |
223 for supp in positive: | 266 for supp in positive: |
224 assert ReadSuppressions(supp.split("\n"), "")[0].Match(stack), \ | 267 parsed = ReadSuppressions(supp.split("\n"), "positive_suppression") |
268 assert parsed[0].Match(stack), \ | |
225 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack) | 269 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack) |
226 for supp in negative: | 270 for supp in negative: |
227 assert not ReadSuppressions(supp.split("\n"), "")[0].Match(stack), \ | 271 parsed = ReadSuppressions(supp.split("\n"), "negative_suppression") |
228 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack) | 272 assert not parsed[0].Match(stack), \ |
273 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack) | |
Alexander Potapenko
2011/12/15 13:07:23
This should be under "not"
Timur Iskhodzhanov
2011/12/15 13:17:27
Done.
| |
229 | 274 |
230 def SelfTest(): | 275 def SelfTest(): |
231 """Tests the Suppression.Match() capabilities.""" | 276 """Tests the Suppression.Match() capabilities.""" |
232 | 277 |
233 test_memcheck_stack_1 = """{ | 278 test_memcheck_stack_1 = """{ |
234 test | 279 test |
235 Memcheck:Leak | 280 Memcheck:Leak |
236 fun:absolutly | 281 fun:absolutly |
237 fun:brilliant | 282 fun:brilliant |
238 obj:condition | 283 obj:condition |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
402 positive_memcheck_suppressions_4, | 447 positive_memcheck_suppressions_4, |
403 negative_memcheck_suppressions_4) | 448 negative_memcheck_suppressions_4) |
404 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions, | 449 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions, |
405 negative_heapcheck_suppressions) | 450 negative_heapcheck_suppressions) |
406 TestStack(test_tsan_stack, positive_tsan_suppressions, | 451 TestStack(test_tsan_stack, positive_tsan_suppressions, |
407 negative_tsan_suppressions) | 452 negative_tsan_suppressions) |
408 | 453 |
409 if __name__ == '__main__': | 454 if __name__ == '__main__': |
410 SelfTest() | 455 SelfTest() |
411 print 'PASS' | 456 print 'PASS' |
OLD | NEW |