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 |