Chromium Code Reviews| 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!\n%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(input_api, output_api): | |
| 219 """A helper function useful in PRESUBMIT.py | |
| 220 Returns a list of errors or []. | |
| 221 """ | |
| 222 sup_regex = re.compile('suppressions.*\.txt$') | |
| 223 filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles() | |
| 224 if sup_regex.search(f.LocalPath())] | |
| 225 | |
| 226 errors = [] | |
| 227 | |
| 228 # TODO(timurrrr): warn on putting suppressions into a wrong file, | |
| 229 # e.g. TSan suppression in a memcheck file. | |
| 230 | |
| 231 for f in filenames: | |
| 232 try: | |
| 233 known_supp_names = {} # Key: name, Value: suppression. | |
| 234 supps = ReadSuppressionsFromFile(f) | |
| 235 for s in supps: | |
| 236 if re.search("<.*suppression.name.here>", s.description): | |
| 237 # Suppression name line is | |
| 238 # <insert_a_suppression_name_here> for Memcheck, | |
| 239 # <Put your suppression name here> for TSan, | |
| 240 # name=<insert_a_suppression_name_here> for DrMemory | |
| 241 errors.append( | |
| 242 SuppressionError( | |
| 243 "You've forgotten to put a suppression name like bug_XXX", | |
| 244 s.defined_at)) | |
| 245 continue | |
| 246 | |
| 247 if s.description in known_supp_names: | |
| 248 errors.append( | |
| 249 SuppressionError( | |
| 250 'Suppression named "%s" is defined more than once, ' | |
| 251 'see %s' % (s.description, | |
| 252 known_supp_names[s.description].defined_at), | |
| 253 s.defined_at)) | |
| 254 else: | |
| 255 known_supp_names[s.description] = s | |
| 256 | |
| 257 except SuppressionError as e: | |
| 258 errors.append(e) | |
| 259 | |
| 260 return [output_api.PresubmitError(str(e)) for e in errors] | |
| 261 | |
| 262 | |
| 215 def TestStack(stack, positive, negative): | 263 def TestStack(stack, positive, negative): |
| 216 """A helper function for SelfTest() that checks a single stack. | 264 """A helper function for SelfTest() that checks a single stack. |
| 217 | 265 |
| 218 Args: | 266 Args: |
| 219 stack: the stack to match the suppressions. | 267 stack: the stack to match the suppressions. |
| 220 positive: the list of suppressions that must match the given stack. | 268 positive: the list of suppressions that must match the given stack. |
| 221 negative: the list of suppressions that should not match. | 269 negative: the list of suppressions that should not match. |
| 222 """ | 270 """ |
| 223 for supp in positive: | 271 for supp in positive: |
| 224 assert ReadSuppressions(supp.split("\n"), "")[0].Match(stack), \ | 272 parsed = ReadSuppressions(supp.split("\n"), "positive_suppression") |
| 225 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack) | 273 assert parsed[0].Match(stack), \ |
| 274 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack) | |
| 226 for supp in negative: | 275 for supp in negative: |
| 227 assert not ReadSuppressions(supp.split("\n"), "")[0].Match(stack), \ | 276 parsed = ReadSuppressions(supp.split("\n"), "negative_suppression") |
| 228 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack) | 277 assert not parsed[0].Match(stack), \ |
| 278 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack) | |
| 229 | 279 |
|
Alexander Potapenko
2011/12/15 13:35:31
Please add a blank line, as this is a top-level de
| |
| 230 def SelfTest(): | 280 def SelfTest(): |
| 231 """Tests the Suppression.Match() capabilities.""" | 281 """Tests the Suppression.Match() capabilities.""" |
| 232 | 282 |
| 233 test_memcheck_stack_1 = """{ | 283 test_memcheck_stack_1 = """{ |
| 234 test | 284 test |
| 235 Memcheck:Leak | 285 Memcheck:Leak |
| 236 fun:absolutly | 286 fun:absolutly |
| 237 fun:brilliant | 287 fun:brilliant |
| 238 obj:condition | 288 obj:condition |
| 239 fun:detection | 289 fun:detection |
| (...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 402 positive_memcheck_suppressions_4, | 452 positive_memcheck_suppressions_4, |
| 403 negative_memcheck_suppressions_4) | 453 negative_memcheck_suppressions_4) |
| 404 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions, | 454 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions, |
| 405 negative_heapcheck_suppressions) | 455 negative_heapcheck_suppressions) |
| 406 TestStack(test_tsan_stack, positive_tsan_suppressions, | 456 TestStack(test_tsan_stack, positive_tsan_suppressions, |
| 407 negative_tsan_suppressions) | 457 negative_tsan_suppressions) |
| 408 | 458 |
| 409 if __name__ == '__main__': | 459 if __name__ == '__main__': |
| 410 SelfTest() | 460 SelfTest() |
| 411 print 'PASS' | 461 print 'PASS' |
| OLD | NEW |