| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 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 | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Valgrind-style suppressions for heapchecker reports. | |
| 7 | |
| 8 Suppressions are defined as follows: | |
| 9 | |
| 10 # optional one-line comments anywhere in the suppressions file. | |
| 11 { | |
| 12 Toolname:Errortype | |
| 13 Short description of the error. | |
| 14 fun:function_name | |
| 15 fun:wildcarded_fun*_name | |
| 16 # an ellipsis wildcards zero or more functions in a stack. | |
| 17 ... | |
| 18 fun:some_other_function_name | |
| 19 } | |
| 20 | |
| 21 Note that only a 'fun:' prefix is allowed, i.e. we can't suppress objects and | |
| 22 source files. | |
| 23 | |
| 24 If ran from the command line, suppressions.py does a self-test of the | |
| 25 Suppression class. | |
| 26 """ | |
| 27 | |
| 28 import re | |
| 29 | |
| 30 ELLIPSIS = '...' | |
| 31 | |
| 32 | |
| 33 class Suppression(object): | |
| 34 """This class represents a single stack trace suppression. | |
| 35 | |
| 36 Attributes: | |
| 37 type: A string representing the error type, e.g. Heapcheck:Leak. | |
| 38 description: A string representing the error description. | |
| 39 """ | |
| 40 | |
| 41 def __init__(self, kind, description, stack): | |
| 42 """Inits Suppression. | |
| 43 | |
| 44 stack is a list of function names and/or wildcards. | |
| 45 | |
| 46 Args: | |
| 47 kind: | |
| 48 description: Same as class attributes. | |
| 49 stack: A list of strings. | |
| 50 """ | |
| 51 self.type = kind | |
| 52 self.description = description | |
| 53 self._stack = stack | |
| 54 re_line = '' | |
| 55 re_bucket = '' | |
| 56 for line in stack: | |
| 57 if line == ELLIPSIS: | |
| 58 re_line += re.escape(re_bucket) | |
| 59 re_bucket = '' | |
| 60 re_line += '(.*\n)*' | |
| 61 else: | |
| 62 for char in line: | |
| 63 if char == '*': | |
| 64 re_line += re.escape(re_bucket) | |
| 65 re_bucket = '' | |
| 66 re_line += '.*' | |
| 67 else: # there can't be any '\*'s in a stack trace | |
| 68 re_bucket += char | |
| 69 re_line += re.escape(re_bucket) | |
| 70 re_bucket = '' | |
| 71 re_line += '\n' | |
| 72 self._re = re.compile(re_line, re.MULTILINE) | |
| 73 | |
| 74 def Match(self, report): | |
| 75 """Returns bool indicating whether the suppression matches the given report. | |
| 76 | |
| 77 Args: | |
| 78 report: list of strings (function names). | |
| 79 Returns: | |
| 80 True if the suppression is not empty and matches the report. | |
| 81 """ | |
| 82 if not self._stack: | |
| 83 return False | |
| 84 if self._re.match('\n'.join(report) + '\n'): | |
| 85 return True | |
| 86 else: | |
| 87 return False | |
| 88 | |
| 89 | |
| 90 class SuppressionError(Exception): | |
| 91 def __init__(self, filename, line, report=''): | |
| 92 Exception.__init__(self, filename, line, report) | |
| 93 self._file = filename | |
| 94 self._line = line | |
| 95 self._report = report | |
| 96 | |
| 97 def __str__(self): | |
| 98 return 'Error reading suppressions from "%s" (line %d): %s.' % ( | |
| 99 self._file, self._line, self._report) | |
| 100 | |
| 101 | |
| 102 def ReadSuppressionsFromFile(filename): | |
| 103 """Given a file, returns a list of suppressions.""" | |
| 104 input_file = file(filename, 'r') | |
| 105 result = [] | |
| 106 cur_descr = '' | |
| 107 cur_type = '' | |
| 108 cur_stack = [] | |
| 109 nline = 0 | |
| 110 try: | |
| 111 for line in input_file: | |
| 112 nline += 1 | |
| 113 line = line.strip() | |
| 114 if line.startswith('#'): | |
| 115 continue | |
| 116 elif line.startswith('{'): | |
| 117 pass | |
| 118 elif line.startswith('}'): | |
| 119 result.append(Suppression(cur_type, cur_descr, cur_stack)) | |
| 120 cur_descr = '' | |
| 121 cur_type = '' | |
| 122 cur_stack = [] | |
| 123 elif not cur_descr: | |
| 124 cur_descr = line | |
| 125 continue | |
| 126 elif not cur_type: | |
| 127 cur_type = line | |
| 128 continue | |
| 129 elif line.startswith('fun:'): | |
| 130 line = line[4:] | |
| 131 cur_stack.append(line.strip()) | |
| 132 elif line.startswith(ELLIPSIS): | |
| 133 cur_stack.append(ELLIPSIS) | |
| 134 else: | |
| 135 raise SuppressionError(filename, nline, | |
| 136 '"fun:function_name" or "..." expected') | |
| 137 except SuppressionError: | |
| 138 input_file.close() | |
| 139 raise | |
| 140 return result | |
| 141 | |
| 142 | |
| 143 def MatchTest(): | |
| 144 """Tests the Suppression.Match() capabilities.""" | |
| 145 | |
| 146 def GenSupp(*lines): | |
| 147 return Suppression('', '', list(lines)) | |
| 148 empty = GenSupp() | |
| 149 assert not empty.Match([]) | |
| 150 assert not empty.Match(['foo', 'bar']) | |
| 151 asterisk = GenSupp('*bar') | |
| 152 assert asterisk.Match(['foobar', 'foobaz']) | |
| 153 assert not asterisk.Match(['foobaz', 'foobar']) | |
| 154 ellipsis = GenSupp('...', 'foo') | |
| 155 assert ellipsis.Match(['foo', 'bar']) | |
| 156 assert ellipsis.Match(['bar', 'baz', 'foo']) | |
| 157 assert not ellipsis.Match(['bar', 'baz', 'bah']) | |
| 158 mixed = GenSupp('...', 'foo*', 'function') | |
| 159 assert mixed.Match(['foobar', 'foobaz', 'function']) | |
| 160 assert not mixed.Match(['foobar', 'blah', 'function']) | |
| 161 at_and_dollar = GenSupp('foo@GLIBC', 'bar@NOCANCEL') | |
| 162 assert at_and_dollar.Match(['foo@GLIBC', 'bar@NOCANCEL']) | |
| 163 re_chars = GenSupp('.*') | |
| 164 assert re_chars.Match(['.foobar']) | |
| 165 assert not re_chars.Match(['foobar']) | |
| 166 print 'PASS' | |
| 167 | |
| 168 | |
| 169 if __name__ == '__main__': | |
| 170 MatchTest() | |
| OLD | NEW |