Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(302)

Side by Side Diff: tools/valgrind/suppressions.py

Issue 8951011: Add presubmit checks for TSan suppressions (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tools/valgrind/tsan/PRESUBMIT.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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)
279
229 280
230 def SelfTest(): 281 def SelfTest():
231 """Tests the Suppression.Match() capabilities.""" 282 """Tests the Suppression.Match() capabilities."""
232 283
233 test_memcheck_stack_1 = """{ 284 test_memcheck_stack_1 = """{
234 test 285 test
235 Memcheck:Leak 286 Memcheck:Leak
236 fun:absolutly 287 fun:absolutly
237 fun:brilliant 288 fun:brilliant
238 obj:condition 289 obj:condition
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after
402 positive_memcheck_suppressions_4, 453 positive_memcheck_suppressions_4,
403 negative_memcheck_suppressions_4) 454 negative_memcheck_suppressions_4)
404 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions, 455 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions,
405 negative_heapcheck_suppressions) 456 negative_heapcheck_suppressions)
406 TestStack(test_tsan_stack, positive_tsan_suppressions, 457 TestStack(test_tsan_stack, positive_tsan_suppressions,
407 negative_tsan_suppressions) 458 negative_tsan_suppressions)
408 459
409 if __name__ == '__main__': 460 if __name__ == '__main__':
410 SelfTest() 461 SelfTest()
411 print 'PASS' 462 print 'PASS'
OLDNEW
« no previous file with comments | « no previous file | tools/valgrind/tsan/PRESUBMIT.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698