cc: Ensure to return pending now before active eventually tiles.
[chromium-blink-merge.git] / tools / valgrind / suppressions.py
blobc655e129689c7db14125953acbeac4df35a854ca
1 #!/usr/bin/env python
2 # Copyright (c) 2012 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.
6 # suppressions.py
8 """Post-process Valgrind suppression matcher.
10 Suppressions are defined as follows:
12 # optional one-line comments anywhere in the suppressions file.
14 <Short description of the error>
15 Toolname:Errortype
16 fun:function_name
17 obj:object_filename
18 fun:wildcarded_fun*_name
19 # an ellipsis wildcards zero or more functions in a stack.
20 ...
21 fun:some_other_function_name
24 If ran from the command line, suppressions.py does a self-test
25 of the Suppression class.
26 """
28 import os
29 import re
30 import sys
32 sys.path.insert(0, os.path.join(os.path.dirname(__file__),
33 '..', 'python', 'google'))
34 import path_utils
37 ELLIPSIS = '...'
40 def GetSuppressions():
41 suppressions_root = path_utils.ScriptDir()
42 JOIN = os.path.join
44 result = {}
46 supp_filename = JOIN(suppressions_root, "memcheck", "suppressions.txt")
47 vg_common = ReadSuppressionsFromFile(supp_filename)
48 supp_filename = JOIN(suppressions_root, "tsan", "suppressions.txt")
49 tsan_common = ReadSuppressionsFromFile(supp_filename)
50 result['common_suppressions'] = vg_common + tsan_common
52 supp_filename = JOIN(suppressions_root, "memcheck", "suppressions_linux.txt")
53 vg_linux = ReadSuppressionsFromFile(supp_filename)
54 supp_filename = JOIN(suppressions_root, "tsan", "suppressions_linux.txt")
55 tsan_linux = ReadSuppressionsFromFile(supp_filename)
56 result['linux_suppressions'] = vg_linux + tsan_linux
58 supp_filename = JOIN(suppressions_root, "memcheck", "suppressions_mac.txt")
59 vg_mac = ReadSuppressionsFromFile(supp_filename)
60 supp_filename = JOIN(suppressions_root, "tsan", "suppressions_mac.txt")
61 tsan_mac = ReadSuppressionsFromFile(supp_filename)
62 result['mac_suppressions'] = vg_mac + tsan_mac
64 supp_filename = JOIN(suppressions_root, "tsan", "suppressions_win32.txt")
65 tsan_win = ReadSuppressionsFromFile(supp_filename)
66 result['win_suppressions'] = tsan_win
68 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions.txt")
69 result['drmem_suppressions'] = ReadSuppressionsFromFile(supp_filename)
70 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions_full.txt")
71 result['drmem_full_suppressions'] = ReadSuppressionsFromFile(supp_filename)
73 return result
76 def GlobToRegex(glob_pattern, ignore_case=False):
77 """Translate glob wildcards (*?) into regex syntax. Escape the rest."""
78 regex = ''
79 for char in glob_pattern:
80 if char == '*':
81 regex += '.*'
82 elif char == '?':
83 regex += '.'
84 elif ignore_case and char.isalpha():
85 regex += '[%s%s]' % (char.lower(), char.upper())
86 else:
87 regex += re.escape(char)
88 return ''.join(regex)
91 def StripAndSkipCommentsIterator(lines):
92 """Generator of (line_no, line) pairs that strips comments and whitespace."""
93 for (line_no, line) in enumerate(lines):
94 line = line.strip() # Drop \n
95 if line.startswith('#'):
96 continue # Comments
97 # Skip comment lines, but not empty lines, they indicate the end of a
98 # suppression. Add one to the line number as well, since most editors use
99 # 1-based numberings, and enumerate is 0-based.
100 yield (line_no + 1, line)
103 class Suppression(object):
104 """This class represents a single stack trace suppression.
106 Attributes:
107 description: A string representing the error description.
108 type: A string representing the error type, e.g. Memcheck:Leak.
109 stack: The lines comprising the stack trace for the suppression.
110 regex: The actual regex used to match against scraped reports.
113 def __init__(self, description, type, stack, defined_at, regex):
114 """Inits Suppression.
116 description, type, stack, regex: same as class attributes
117 defined_at: file:line identifying where the suppression was defined
119 self.description = description
120 self.type = type
121 self.stack = stack
122 self.defined_at = defined_at
123 self.regex = re.compile(regex, re.MULTILINE)
125 def Match(self, suppression_from_report):
126 """Returns bool indicating whether this suppression matches
127 the suppression generated from Valgrind error report.
129 We match our suppressions against generated suppressions
130 (not against reports) since they have the same format
131 while the reports are taken from XML, contain filenames,
132 they are demangled, and are generally more difficult to
133 parse.
135 Args:
136 suppression_from_report: list of strings (function names).
137 Returns:
138 True if the suppression is not empty and matches the report.
140 if not self.stack:
141 return False
142 lines = [f.strip() for f in suppression_from_report]
143 return self.regex.match('\n'.join(lines) + '\n') is not None
146 def FilenameToTool(filename):
147 """Return the name of the tool that a file is related to, or None.
149 Example mappings:
150 tools/valgrind/tsan/suppressions.txt -> tsan
151 tools/valgrind/drmemory/suppressions.txt -> drmemory
152 tools/valgrind/drmemory/suppressions_full.txt -> drmemory
153 tools/valgrind/memcheck/suppressions.txt -> memcheck
154 tools/valgrind/memcheck/suppressions_mac.txt -> memcheck
156 filename = os.path.abspath(filename)
157 parts = filename.split(os.sep)
158 tool = parts[-2]
159 if tool in ('drmemory', 'memcheck', 'tsan'):
160 return tool
161 return None
164 def ReadSuppressionsFromFile(filename):
165 """Read suppressions from the given file and return them as a list"""
166 tool_to_parser = {
167 "drmemory": ReadDrMemorySuppressions,
168 "memcheck": ReadValgrindStyleSuppressions,
169 "tsan": ReadValgrindStyleSuppressions,
171 tool = FilenameToTool(filename)
172 assert tool in tool_to_parser, (
173 "unknown tool %s for filename %s" % (tool, filename))
174 parse_func = tool_to_parser[tool]
176 # Consider non-existent files to be empty.
177 if not os.path.exists(filename):
178 return []
180 input_file = file(filename, 'r')
181 try:
182 return parse_func(input_file, filename)
183 except SuppressionError:
184 input_file.close()
185 raise
188 class ValgrindStyleSuppression(Suppression):
189 """A suppression using the Valgrind syntax.
191 Most tools, even ones that are not Valgrind-based, use this syntax, ie
192 TSan, etc.
194 Attributes:
195 Same as Suppression.
198 def __init__(self, description, type, stack, defined_at):
199 """Creates a suppression using the Memcheck and TSan, syntax."""
200 regex = '{\n.*\n%s\n' % type
201 for line in stack:
202 if line == ELLIPSIS:
203 regex += '(.*\n)*'
204 else:
205 regex += GlobToRegex(line)
206 regex += '\n'
207 regex += '(.*\n)*'
208 regex += '}'
210 # In the recent version of valgrind-variant we've switched
211 # from memcheck's default Addr[1248]/Value[1248]/Cond suppression types
212 # to simply Unaddressable/Uninitialized.
213 # The suppression generator no longer gives us "old" types thus
214 # for the "new-type" suppressions:
215 # * Memcheck:Unaddressable should also match Addr* reports,
216 # * Memcheck:Uninitialized should also match Cond and Value reports,
218 # We also want to support legacy suppressions (e.g. copied from
219 # upstream bugs etc), so:
220 # * Memcheck:Addr[1248] suppressions should match Unaddressable reports,
221 # * Memcheck:Cond and Memcheck:Value[1248] should match Uninitialized.
222 # Please note the latest two rules only apply to the
223 # tools/valgrind/waterfall.sh suppression matcher and the real
224 # valgrind-variant Memcheck will not suppress
225 # e.g. Addr1 printed as Unaddressable with Addr4 suppression.
226 # Be careful to check the access size while copying legacy suppressions!
227 for sz in [1, 2, 4, 8]:
228 regex = regex.replace("\nMemcheck:Addr%d\n" % sz,
229 "\nMemcheck:(Addr%d|Unaddressable)\n" % sz)
230 regex = regex.replace("\nMemcheck:Value%d\n" % sz,
231 "\nMemcheck:(Value%d|Uninitialized)\n" % sz)
232 regex = regex.replace("\nMemcheck:Cond\n",
233 "\nMemcheck:(Cond|Uninitialized)\n")
234 regex = regex.replace("\nMemcheck:Unaddressable\n",
235 "\nMemcheck:(Addr.|Unaddressable)\n")
236 regex = regex.replace("\nMemcheck:Uninitialized\n",
237 "\nMemcheck:(Cond|Value.|Uninitialized)\n")
239 return super(ValgrindStyleSuppression, self).__init__(
240 description, type, stack, defined_at, regex)
242 def __str__(self):
243 """Stringify."""
244 lines = [self.description, self.type] + self.stack
245 return "{\n %s\n}\n" % "\n ".join(lines)
248 class SuppressionError(Exception):
249 def __init__(self, message, happened_at):
250 self._message = message
251 self._happened_at = happened_at
253 def __str__(self):
254 return 'Error reading suppressions at %s!\n%s' % (
255 self._happened_at, self._message)
258 def ReadValgrindStyleSuppressions(lines, supp_descriptor):
259 """Given a list of lines, returns a list of suppressions.
261 Args:
262 lines: a list of lines containing suppressions.
263 supp_descriptor: should typically be a filename.
264 Used only when printing errors.
266 result = []
267 cur_descr = ''
268 cur_type = ''
269 cur_stack = []
270 in_suppression = False
271 nline = 0
272 for line in lines:
273 nline += 1
274 line = line.strip()
275 if line.startswith('#'):
276 continue
277 if not in_suppression:
278 if not line:
279 # empty lines between suppressions
280 pass
281 elif line.startswith('{'):
282 in_suppression = True
283 pass
284 else:
285 raise SuppressionError('Expected: "{"',
286 "%s:%d" % (supp_descriptor, nline))
287 elif line.startswith('}'):
288 result.append(
289 ValgrindStyleSuppression(cur_descr, cur_type, cur_stack,
290 "%s:%d" % (supp_descriptor, nline)))
291 cur_descr = ''
292 cur_type = ''
293 cur_stack = []
294 in_suppression = False
295 elif not cur_descr:
296 cur_descr = line
297 continue
298 elif not cur_type:
299 if (not line.startswith("Memcheck:") and
300 not line.startswith("ThreadSanitizer:")):
301 raise SuppressionError(
302 'Expected "Memcheck:TYPE" or "ThreadSanitizer:TYPE", '
303 'got "%s"' % line,
304 "%s:%d" % (supp_descriptor, nline))
305 supp_type = line.split(':')[1]
306 if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8",
307 "Cond", "Free", "Jump", "Leak", "Overlap", "Param",
308 "Value1", "Value2", "Value4", "Value8",
309 "Race", "UnlockNonLocked", "InvalidLock",
310 "Unaddressable", "Uninitialized"]:
311 raise SuppressionError('Unknown suppression type "%s"' % supp_type,
312 "%s:%d" % (supp_descriptor, nline))
313 cur_type = line
314 continue
315 elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line):
316 cur_stack.append(line.strip())
317 elif len(cur_stack) == 0 and cur_type == "Memcheck:Param":
318 cur_stack.append(line.strip())
319 else:
320 raise SuppressionError(
321 '"fun:function_name" or "obj:object_file" or "..." expected',
322 "%s:%d" % (supp_descriptor, nline))
323 return result
326 def PresubmitCheckSuppressions(supps):
327 """Check a list of suppressions and return a list of SuppressionErrors.
329 Mostly useful for separating the checking logic from the Presubmit API for
330 testing.
332 known_supp_names = {} # Key: name, Value: suppression.
333 errors = []
334 for s in supps:
335 if re.search("<.*suppression.name.here>", s.description):
336 # Suppression name line is
337 # <insert_a_suppression_name_here> for Memcheck,
338 # <Put your suppression name here> for TSan,
339 # name=<insert_a_suppression_name_here> for DrMemory
340 errors.append(
341 SuppressionError(
342 "You've forgotten to put a suppression name like bug_XXX",
343 s.defined_at))
344 continue
346 if s.description in known_supp_names:
347 errors.append(
348 SuppressionError(
349 'Suppression named "%s" is defined more than once, '
350 'see %s' % (s.description,
351 known_supp_names[s.description].defined_at),
352 s.defined_at))
353 else:
354 known_supp_names[s.description] = s
355 return errors
358 def PresubmitCheck(input_api, output_api):
359 """A helper function useful in PRESUBMIT.py
360 Returns a list of errors or [].
362 sup_regex = re.compile('suppressions.*\.txt$')
363 filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles()
364 if sup_regex.search(f.LocalPath())]
366 errors = []
368 # TODO(timurrrr): warn on putting suppressions into a wrong file,
369 # e.g. TSan suppression in a memcheck file.
371 for f in filenames:
372 try:
373 supps = ReadSuppressionsFromFile(f)
374 errors.extend(PresubmitCheckSuppressions(supps))
375 except SuppressionError as e:
376 errors.append(e)
378 return [output_api.PresubmitError(str(e)) for e in errors]
381 class DrMemorySuppression(Suppression):
382 """A suppression using the DrMemory syntax.
384 Attributes:
385 instr: The instruction to match.
386 Rest inherited from Suppression.
389 def __init__(self, name, report_type, instr, stack, defined_at):
390 """Constructor."""
391 self.instr = instr
393 # Construct the regex.
394 regex = '{\n'
395 if report_type == 'LEAK':
396 regex += '(POSSIBLE )?LEAK'
397 else:
398 regex += report_type
399 regex += '\nname=.*\n'
401 # TODO(rnk): Implement http://crbug.com/107416#c5 .
402 # drmemory_analyze.py doesn't generate suppressions with an instruction in
403 # them, so these suppressions will always fail to match. We should override
404 # Match to fetch the instruction from the report and try to match against
405 # that.
406 if instr:
407 regex += 'instruction=%s\n' % GlobToRegex(instr)
409 for line in stack:
410 if line == ELLIPSIS:
411 regex += '(.*\n)*'
412 elif '!' in line:
413 (mod, func) = line.split('!')
414 if func == ELLIPSIS: # mod!ellipsis frame
415 regex += '(%s\!.*\n)+' % GlobToRegex(mod, ignore_case=True)
416 else: # mod!func frame
417 # Ignore case for the module match, but not the function match.
418 regex += '%s\!%s\n' % (GlobToRegex(mod, ignore_case=True),
419 GlobToRegex(func, ignore_case=False))
420 else:
421 regex += GlobToRegex(line)
422 regex += '\n'
423 regex += '(.*\n)*' # Match anything left in the stack.
424 regex += '}'
425 return super(DrMemorySuppression, self).__init__(name, report_type, stack,
426 defined_at, regex)
428 def __str__(self):
429 """Stringify."""
430 text = self.type + "\n"
431 if self.description:
432 text += "name=%s\n" % self.description
433 if self.instr:
434 text += "instruction=%s\n" % self.instr
435 text += "\n".join(self.stack)
436 text += "\n"
437 return text
440 # Possible DrMemory error report types. Keep consistent with suppress_name
441 # array in drmemory/drmemory/report.c.
442 DRMEMORY_ERROR_TYPES = [
443 'UNADDRESSABLE ACCESS',
444 'UNINITIALIZED READ',
445 'INVALID HEAP ARGUMENT',
446 'GDI USAGE ERROR',
447 'HANDLE LEAK',
448 'LEAK',
449 'POSSIBLE LEAK',
450 'WARNING',
454 # Regexes to match valid drmemory frames.
455 DRMEMORY_FRAME_PATTERNS = [
456 re.compile(r"^.*\!.*$"), # mod!func
457 re.compile(r"^.*!\.\.\.$"), # mod!ellipsis
458 re.compile(r"^\<.*\+0x.*\>$"), # <mod+0xoffs>
459 re.compile(r"^\<not in a module\>$"),
460 re.compile(r"^system call .*$"),
461 re.compile(r"^\*$"), # wildcard
462 re.compile(r"^\.\.\.$"), # ellipsis
466 def ReadDrMemorySuppressions(lines, supp_descriptor):
467 """Given a list of lines, returns a list of DrMemory suppressions.
469 Args:
470 lines: a list of lines containing suppressions.
471 supp_descriptor: should typically be a filename.
472 Used only when parsing errors happen.
474 lines = StripAndSkipCommentsIterator(lines)
475 suppressions = []
476 for (line_no, line) in lines:
477 if not line:
478 continue
479 if line not in DRMEMORY_ERROR_TYPES:
480 raise SuppressionError('Expected a DrMemory error type, '
481 'found %r instead\n Valid error types: %s' %
482 (line, ' '.join(DRMEMORY_ERROR_TYPES)),
483 "%s:%d" % (supp_descriptor, line_no))
485 # Suppression starts here.
486 report_type = line
487 name = ''
488 instr = None
489 stack = []
490 defined_at = "%s:%d" % (supp_descriptor, line_no)
491 found_stack = False
492 for (line_no, line) in lines:
493 if not found_stack and line.startswith('name='):
494 name = line.replace('name=', '')
495 elif not found_stack and line.startswith('instruction='):
496 instr = line.replace('instruction=', '')
497 else:
498 # Unrecognized prefix indicates start of stack trace.
499 found_stack = True
500 if not line:
501 # Blank line means end of suppression.
502 break
503 if not any([regex.match(line) for regex in DRMEMORY_FRAME_PATTERNS]):
504 raise SuppressionError(
505 ('Unexpected stack frame pattern at line %d\n' +
506 'Frames should be one of the following:\n' +
507 ' module!function\n' +
508 ' module!...\n' +
509 ' <module+0xhexoffset>\n' +
510 ' <not in a module>\n' +
511 ' system call Name\n' +
512 ' *\n' +
513 ' ...\n') % line_no, defined_at)
514 stack.append(line)
516 if len(stack) == 0: # In case we hit EOF or blank without any stack frames.
517 raise SuppressionError('Suppression "%s" has no stack frames, ends at %d'
518 % (name, line_no), defined_at)
519 if stack[-1] == ELLIPSIS:
520 raise SuppressionError('Suppression "%s" ends in an ellipsis on line %d' %
521 (name, line_no), defined_at)
523 suppressions.append(
524 DrMemorySuppression(name, report_type, instr, stack, defined_at))
526 return suppressions
529 def ParseSuppressionOfType(lines, supp_descriptor, def_line_no, report_type):
530 """Parse the suppression starting on this line.
532 Suppressions start with a type, have an optional name and instruction, and a
533 stack trace that ends in a blank line.
538 def TestStack(stack, positive, negative, suppression_parser=None):
539 """A helper function for SelfTest() that checks a single stack.
541 Args:
542 stack: the stack to match the suppressions.
543 positive: the list of suppressions that must match the given stack.
544 negative: the list of suppressions that should not match.
545 suppression_parser: optional arg for the suppression parser, default is
546 ReadValgrindStyleSuppressions.
548 if not suppression_parser:
549 suppression_parser = ReadValgrindStyleSuppressions
550 for supp in positive:
551 parsed = suppression_parser(supp.split("\n"), "positive_suppression")
552 assert parsed[0].Match(stack.split("\n")), (
553 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack))
554 for supp in negative:
555 parsed = suppression_parser(supp.split("\n"), "negative_suppression")
556 assert not parsed[0].Match(stack.split("\n")), (
557 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack))
560 def TestFailPresubmit(supp_text, error_text, suppression_parser=None):
561 """A helper function for SelfTest() that verifies a presubmit check fires.
563 Args:
564 supp_text: suppression text to parse.
565 error_text: text of the presubmit error we expect to find.
566 suppression_parser: optional arg for the suppression parser, default is
567 ReadValgrindStyleSuppressions.
569 if not suppression_parser:
570 suppression_parser = ReadValgrindStyleSuppressions
571 try:
572 supps = suppression_parser(supp_text.split("\n"), "<presubmit suppression>")
573 except SuppressionError, e:
574 # If parsing raised an exception, match the error text here.
575 assert error_text in str(e), (
576 "presubmit text %r not in SuppressionError:\n%r" %
577 (error_text, str(e)))
578 else:
579 # Otherwise, run the presubmit checks over the supps. We expect a single
580 # error that has text matching error_text.
581 errors = PresubmitCheckSuppressions(supps)
582 assert len(errors) == 1, (
583 "expected exactly one presubmit error, got:\n%s" % errors)
584 assert error_text in str(errors[0]), (
585 "presubmit text %r not in SuppressionError:\n%r" %
586 (error_text, str(errors[0])))
589 def SelfTest():
590 """Tests the Suppression.Match() capabilities."""
592 test_memcheck_stack_1 = """{
593 test
594 Memcheck:Leak
595 fun:absolutly
596 fun:brilliant
597 obj:condition
598 fun:detection
599 fun:expression
600 }"""
602 test_memcheck_stack_2 = """{
603 test
604 Memcheck:Uninitialized
605 fun:absolutly
606 fun:brilliant
607 obj:condition
608 fun:detection
609 fun:expression
610 }"""
612 test_memcheck_stack_3 = """{
613 test
614 Memcheck:Unaddressable
615 fun:absolutly
616 fun:brilliant
617 obj:condition
618 fun:detection
619 fun:expression
620 }"""
622 test_memcheck_stack_4 = """{
623 test
624 Memcheck:Addr4
625 fun:absolutly
626 fun:brilliant
627 obj:condition
628 fun:detection
629 fun:expression
630 }"""
632 test_tsan_stack = """{
633 test
634 ThreadSanitizer:Race
635 fun:absolutly
636 fun:brilliant
637 obj:condition
638 fun:detection
639 fun:expression
640 }"""
643 positive_memcheck_suppressions_1 = [
644 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
645 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n}",
646 "{\nzzz\nMemcheck:Leak\nfun:absolutly\nfun:brilliant\n}",
647 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\n}",
648 "{\nzzz\nMemcheck:Leak\n...\nfun:detection\n}",
649 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n...\nfun:detection\n}",
650 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n...\nfun:detection\n}",
651 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\n}",
652 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\nfun:detection\n}",
653 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\nobj:condition\n}",
656 positive_memcheck_suppressions_2 = [
657 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
658 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*ly\n}",
659 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\nfun:brilliant\n}",
660 # Legacy suppression types
661 "{\nzzz\nMemcheck:Value1\n...\nfun:brilliant\n}",
662 "{\nzzz\nMemcheck:Cond\n...\nfun:detection\n}",
663 "{\nzzz\nMemcheck:Value8\nfun:absolutly\nfun:brilliant\n}",
666 positive_memcheck_suppressions_3 = [
667 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
668 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
669 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
670 # Legacy suppression types
671 "{\nzzz\nMemcheck:Addr1\n...\nfun:brilliant\n}",
672 "{\nzzz\nMemcheck:Addr8\n...\nfun:detection\n}",
675 positive_memcheck_suppressions_4 = [
676 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\n}",
677 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
678 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\nfun:brilliant\n}",
679 "{\nzzz\nMemcheck:Unaddressable\n...\nfun:brilliant\n}",
680 "{\nzzz\nMemcheck:Addr4\n...\nfun:detection\n}",
683 positive_tsan_suppressions = [
684 "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}",
685 "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}",
688 negative_memcheck_suppressions_1 = [
689 "{\nzzz\nMemcheck:Leak\nfun:abnormal\n}",
690 "{\nzzz\nMemcheck:Leak\nfun:ab*liant\n}",
691 "{\nzzz\nMemcheck:Leak\nfun:brilliant\n}",
692 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
693 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
696 negative_memcheck_suppressions_2 = [
697 "{\nzzz\nMemcheck:Cond\nfun:abnormal\n}",
698 "{\nzzz\nMemcheck:Value2\nfun:abnormal\n}",
699 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*liant\n}",
700 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
701 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
702 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
703 "{\nzzz\nMemcheck:Unaddressable\nfun:brilliant\n}",
706 negative_memcheck_suppressions_3 = [
707 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
708 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
709 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
710 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
711 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
712 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
715 negative_memcheck_suppressions_4 = [
716 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
717 "{\nzzz\nMemcheck:Addr4\nfun:abnormal\n}",
718 "{\nzzz\nMemcheck:Unaddressable\nfun:abnormal\n}",
719 "{\nzzz\nMemcheck:Addr1\nfun:absolutly\n}",
720 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
721 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
722 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
723 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
726 negative_tsan_suppressions = [
727 "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}",
728 "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}",
731 TestStack(test_memcheck_stack_1,
732 positive_memcheck_suppressions_1,
733 negative_memcheck_suppressions_1)
734 TestStack(test_memcheck_stack_2,
735 positive_memcheck_suppressions_2,
736 negative_memcheck_suppressions_2)
737 TestStack(test_memcheck_stack_3,
738 positive_memcheck_suppressions_3,
739 negative_memcheck_suppressions_3)
740 TestStack(test_memcheck_stack_4,
741 positive_memcheck_suppressions_4,
742 negative_memcheck_suppressions_4)
743 TestStack(test_tsan_stack, positive_tsan_suppressions,
744 negative_tsan_suppressions)
746 # TODO(timurrrr): add TestFailPresubmit tests.
748 ### DrMemory self tests.
750 # http://crbug.com/96010 suppression.
751 stack_96010 = """{
752 UNADDRESSABLE ACCESS
753 name=<insert_a_suppression_name_here>
754 *!TestingProfile::FinishInit
755 *!TestingProfile::TestingProfile
756 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
757 *!testing::Test::Run
758 }"""
760 suppress_96010 = [
761 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n",
762 ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" +
763 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"),
764 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n",
765 "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n",
766 # No name should be needed
767 "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n",
768 # Whole trace
769 ("UNADDRESSABLE ACCESS\n" +
770 "*!TestingProfile::FinishInit\n" +
771 "*!TestingProfile::TestingProfile\n" +
772 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
773 "*!testing::Test::Run\n"),
776 negative_96010 = [
777 # Wrong type
778 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
779 # No ellipsis
780 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
783 TestStack(stack_96010, suppress_96010, negative_96010,
784 suppression_parser=ReadDrMemorySuppressions)
786 # Invalid heap arg
787 stack_invalid = """{
788 INVALID HEAP ARGUMENT
789 name=asdf
790 *!foo
791 }"""
792 suppress_invalid = [
793 "INVALID HEAP ARGUMENT\n*!foo\n",
795 negative_invalid = [
796 "UNADDRESSABLE ACCESS\n*!foo\n",
799 TestStack(stack_invalid, suppress_invalid, negative_invalid,
800 suppression_parser=ReadDrMemorySuppressions)
802 # Suppress only ntdll
803 stack_in_ntdll = """{
804 UNADDRESSABLE ACCESS
805 name=<insert_a_suppression_name_here>
806 ntdll.dll!RtlTryEnterCriticalSection
807 }"""
808 stack_not_ntdll = """{
809 UNADDRESSABLE ACCESS
810 name=<insert_a_suppression_name_here>
811 notntdll.dll!RtlTryEnterCriticalSection
812 }"""
814 suppress_in_ntdll = [
815 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
817 suppress_in_any = [
818 "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
821 TestStack(stack_in_ntdll, suppress_in_ntdll + suppress_in_any, [],
822 suppression_parser=ReadDrMemorySuppressions)
823 # Make sure we don't wildcard away the "not" part and match ntdll.dll by
824 # accident.
825 TestStack(stack_not_ntdll, suppress_in_any, suppress_in_ntdll,
826 suppression_parser=ReadDrMemorySuppressions)
828 # Suppress a POSSIBLE LEAK with LEAK.
829 stack_foo_possible = """{
830 POSSIBLE LEAK
831 name=foo possible
832 *!foo
833 }"""
834 suppress_foo_possible = [ "POSSIBLE LEAK\n*!foo\n" ]
835 suppress_foo_leak = [ "LEAK\n*!foo\n" ]
836 TestStack(stack_foo_possible, suppress_foo_possible + suppress_foo_leak, [],
837 suppression_parser=ReadDrMemorySuppressions)
839 # Don't suppress LEAK with POSSIBLE LEAK.
840 stack_foo_leak = """{
841 LEAK
842 name=foo leak
843 *!foo
844 }"""
845 TestStack(stack_foo_leak, suppress_foo_leak, suppress_foo_possible,
846 suppression_parser=ReadDrMemorySuppressions)
848 # Test case insensitivity of module names.
849 stack_user32_mixed_case = """{
850 LEAK
851 name=<insert>
852 USER32.dll!foo
853 user32.DLL!bar
854 user32.dll!baz
855 }"""
856 suppress_user32 = [ # Module name case doesn't matter.
857 "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n",
858 "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n",
860 no_suppress_user32 = [ # Function name case matters.
861 "LEAK\nuser32.dll!FOO\nuser32.dll!BAR\nuser32.dll!BAZ\n",
862 "LEAK\nUSER32.DLL!FOO\nUSER32.DLL!BAR\nUSER32.DLL!BAZ\n",
864 TestStack(stack_user32_mixed_case, suppress_user32, no_suppress_user32,
865 suppression_parser=ReadDrMemorySuppressions)
867 # Test mod!... frames.
868 stack_kernel32_through_ntdll = """{
869 LEAK
870 name=<insert>
871 kernel32.dll!foo
872 KERNEL32.dll!bar
873 kernel32.DLL!baz
874 ntdll.dll!quux
875 }"""
876 suppress_mod_ellipsis = [
877 "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
878 "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
880 no_suppress_mod_ellipsis = [
881 # Need one or more matching frames, not zero, unlike regular ellipsis.
882 "LEAK\nuser32.dll!...\nkernel32.dll!...\nntdll.dll!quux\n",
884 TestStack(stack_kernel32_through_ntdll, suppress_mod_ellipsis,
885 no_suppress_mod_ellipsis,
886 suppression_parser=ReadDrMemorySuppressions)
888 # Test that the presubmit checks work.
889 forgot_to_name = """
890 UNADDRESSABLE ACCESS
891 name=<insert_a_suppression_name_here>
892 ntdll.dll!RtlTryEnterCriticalSection
894 TestFailPresubmit(forgot_to_name, 'forgotten to put a suppression',
895 suppression_parser=ReadDrMemorySuppressions)
897 named_twice = """
898 UNADDRESSABLE ACCESS
899 name=http://crbug.com/1234
900 *!foo
902 UNADDRESSABLE ACCESS
903 name=http://crbug.com/1234
904 *!bar
906 TestFailPresubmit(named_twice, 'defined more than once',
907 suppression_parser=ReadDrMemorySuppressions)
909 forgot_stack = """
910 UNADDRESSABLE ACCESS
911 name=http://crbug.com/1234
913 TestFailPresubmit(forgot_stack, 'has no stack frames',
914 suppression_parser=ReadDrMemorySuppressions)
916 ends_in_ellipsis = """
917 UNADDRESSABLE ACCESS
918 name=http://crbug.com/1234
919 ntdll.dll!RtlTryEnterCriticalSection
922 TestFailPresubmit(ends_in_ellipsis, 'ends in an ellipsis',
923 suppression_parser=ReadDrMemorySuppressions)
925 bad_stack_frame = """
926 UNADDRESSABLE ACCESS
927 name=http://crbug.com/1234
928 fun:memcheck_style_frame
930 TestFailPresubmit(bad_stack_frame, 'Unexpected stack frame pattern',
931 suppression_parser=ReadDrMemorySuppressions)
933 # Test FilenameToTool.
934 filenames_to_tools = {
935 "tools/valgrind/tsan/suppressions.txt": "tsan",
936 "tools/valgrind/drmemory/suppressions.txt": "drmemory",
937 "tools/valgrind/drmemory/suppressions_full.txt": "drmemory",
938 "tools/valgrind/memcheck/suppressions.txt": "memcheck",
939 "tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
940 "asdf/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
941 "foo/bar/baz/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
942 "foo/bar/baz/tools/valgrind/suppressions.txt": None,
943 "tools/valgrind/suppressions.txt": None,
945 for (filename, expected_tool) in filenames_to_tools.items():
946 filename.replace('/', os.sep) # Make the path look native.
947 tool = FilenameToTool(filename)
948 assert tool == expected_tool, (
949 "failed to get expected tool for filename %r, expected %s, got %s" %
950 (filename, expected_tool, tool))
952 # Test ValgrindStyleSuppression.__str__.
953 supp = ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
954 ["...", "fun:foo"], "supp.txt:1")
955 # Intentional 3-space indent. =/
956 supp_str = ("{\n"
957 " http://crbug.com/1234\n"
958 " Memcheck:Leak\n"
959 " ...\n"
960 " fun:foo\n"
961 "}\n")
962 assert str(supp) == supp_str, (
963 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
965 # Test DrMemorySuppression.__str__.
966 supp = DrMemorySuppression(
967 "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
968 supp_str = ("LEAK\n"
969 "name=http://crbug.com/1234\n"
970 "...\n"
971 "*!foo\n")
972 assert str(supp) == supp_str, (
973 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
975 supp = DrMemorySuppression(
976 "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01",
977 ["ntdll.dll!*", "*!foo"], "supp.txt:1")
978 supp_str = ("UNINITIALIZED READ\n"
979 "name=http://crbug.com/1234\n"
980 "instruction=test 0x08(%eax) $0x01\n"
981 "ntdll.dll!*\n"
982 "*!foo\n")
983 assert str(supp) == supp_str, (
984 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
987 if __name__ == '__main__':
988 SelfTest()
989 print 'PASS'