Disable ContentSettingBubbleModelTest.RPHAllow which is flaky.
[chromium-blink-merge.git] / tools / valgrind / suppressions.py
blob48955984d2cb39191e0c5f18d8da067bec680a66
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, "..", "heapcheck", "suppressions.txt")
69 result['heapcheck_suppressions'] = ReadSuppressionsFromFile(supp_filename)
71 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions.txt")
72 result['drmem_suppressions'] = ReadSuppressionsFromFile(supp_filename)
73 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions_full.txt")
74 result['drmem_full_suppressions'] = ReadSuppressionsFromFile(supp_filename)
76 return result
79 def GlobToRegex(glob_pattern, ignore_case=False):
80 """Translate glob wildcards (*?) into regex syntax. Escape the rest."""
81 regex = ''
82 for char in glob_pattern:
83 if char == '*':
84 regex += '.*'
85 elif char == '?':
86 regex += '.'
87 elif ignore_case and char.isalpha():
88 regex += '[%s%s]' % (char.lower(), char.upper())
89 else:
90 regex += re.escape(char)
91 return ''.join(regex)
94 def StripAndSkipCommentsIterator(lines):
95 """Generator of (line_no, line) pairs that strips comments and whitespace."""
96 for (line_no, line) in enumerate(lines):
97 line = line.strip() # Drop \n
98 if line.startswith('#'):
99 continue # Comments
100 # Skip comment lines, but not empty lines, they indicate the end of a
101 # suppression. Add one to the line number as well, since most editors use
102 # 1-based numberings, and enumerate is 0-based.
103 yield (line_no + 1, line)
106 class Suppression(object):
107 """This class represents a single stack trace suppression.
109 Attributes:
110 description: A string representing the error description.
111 type: A string representing the error type, e.g. Memcheck:Leak.
112 stack: The lines comprising the stack trace for the suppression.
113 regex: The actual regex used to match against scraped reports.
116 def __init__(self, description, type, stack, defined_at, regex):
117 """Inits Suppression.
119 description, type, stack, regex: same as class attributes
120 defined_at: file:line identifying where the suppression was defined
122 self.description = description
123 self.type = type
124 self.stack = stack
125 self.defined_at = defined_at
126 self.regex = re.compile(regex, re.MULTILINE)
128 def Match(self, suppression_from_report):
129 """Returns bool indicating whether this suppression matches
130 the suppression generated from Valgrind error report.
132 We match our suppressions against generated suppressions
133 (not against reports) since they have the same format
134 while the reports are taken from XML, contain filenames,
135 they are demangled, and are generally more difficult to
136 parse.
138 Args:
139 suppression_from_report: list of strings (function names).
140 Returns:
141 True if the suppression is not empty and matches the report.
143 if not self.stack:
144 return False
145 lines = [f.strip() for f in suppression_from_report]
146 return self.regex.match('\n'.join(lines) + '\n') is not None
149 def FilenameToTool(filename):
150 """Return the name of the tool that a file is related to, or None.
152 Example mappings:
153 tools/heapcheck/suppressions.txt -> heapcheck
154 tools/valgrind/tsan/suppressions.txt -> tsan
155 tools/valgrind/drmemory/suppressions.txt -> drmemory
156 tools/valgrind/drmemory/suppressions_full.txt -> drmemory
157 tools/valgrind/memcheck/suppressions.txt -> memcheck
158 tools/valgrind/memcheck/suppressions_mac.txt -> memcheck
160 filename = os.path.abspath(filename)
161 parts = filename.split(os.sep)
162 tool = parts[-2]
163 if tool in ('heapcheck', 'drmemory', 'memcheck', 'tsan'):
164 return tool
165 return None
168 def ReadSuppressionsFromFile(filename):
169 """Read suppressions from the given file and return them as a list"""
170 tool_to_parser = {
171 "drmemory": ReadDrMemorySuppressions,
172 "memcheck": ReadValgrindStyleSuppressions,
173 "tsan": ReadValgrindStyleSuppressions,
174 "heapcheck": ReadValgrindStyleSuppressions,
176 tool = FilenameToTool(filename)
177 assert tool in tool_to_parser, (
178 "unknown tool %s for filename %s" % (tool, filename))
179 parse_func = tool_to_parser[tool]
181 # Consider non-existent files to be empty.
182 if not os.path.exists(filename):
183 return []
185 input_file = file(filename, 'r')
186 try:
187 return parse_func(input_file, filename)
188 except SuppressionError:
189 input_file.close()
190 raise
193 class ValgrindStyleSuppression(Suppression):
194 """A suppression using the Valgrind syntax.
196 Most tools, even ones that are not Valgrind-based, use this syntax, ie
197 Heapcheck, TSan, etc.
199 Attributes:
200 Same as Suppression.
203 def __init__(self, description, type, stack, defined_at):
204 """Creates a suppression using the Memcheck, TSan, and Heapcheck syntax."""
205 regex = '{\n.*\n%s\n' % type
206 for line in stack:
207 if line == ELLIPSIS:
208 regex += '(.*\n)*'
209 else:
210 regex += GlobToRegex(line)
211 regex += '\n'
212 regex += '(.*\n)*'
213 regex += '}'
215 # In the recent version of valgrind-variant we've switched
216 # from memcheck's default Addr[1248]/Value[1248]/Cond suppression types
217 # to simply Unaddressable/Uninitialized.
218 # The suppression generator no longer gives us "old" types thus
219 # for the "new-type" suppressions:
220 # * Memcheck:Unaddressable should also match Addr* reports,
221 # * Memcheck:Uninitialized should also match Cond and Value reports,
223 # We also want to support legacy suppressions (e.g. copied from
224 # upstream bugs etc), so:
225 # * Memcheck:Addr[1248] suppressions should match Unaddressable reports,
226 # * Memcheck:Cond and Memcheck:Value[1248] should match Uninitialized.
227 # Please note the latest two rules only apply to the
228 # tools/valgrind/waterfall.sh suppression matcher and the real
229 # valgrind-variant Memcheck will not suppress
230 # e.g. Addr1 printed as Unaddressable with Addr4 suppression.
231 # Be careful to check the access size while copying legacy suppressions!
232 for sz in [1, 2, 4, 8]:
233 regex = regex.replace("\nMemcheck:Addr%d\n" % sz,
234 "\nMemcheck:(Addr%d|Unaddressable)\n" % sz)
235 regex = regex.replace("\nMemcheck:Value%d\n" % sz,
236 "\nMemcheck:(Value%d|Uninitialized)\n" % sz)
237 regex = regex.replace("\nMemcheck:Cond\n",
238 "\nMemcheck:(Cond|Uninitialized)\n")
239 regex = regex.replace("\nMemcheck:Unaddressable\n",
240 "\nMemcheck:(Addr.|Unaddressable)\n")
241 regex = regex.replace("\nMemcheck:Uninitialized\n",
242 "\nMemcheck:(Cond|Value.|Uninitialized)\n")
244 return super(ValgrindStyleSuppression, self).__init__(
245 description, type, stack, defined_at, regex)
247 def __str__(self):
248 """Stringify."""
249 lines = [self.description, self.type] + self.stack
250 return "{\n %s\n}\n" % "\n ".join(lines)
253 class SuppressionError(Exception):
254 def __init__(self, message, happened_at):
255 self._message = message
256 self._happened_at = happened_at
258 def __str__(self):
259 return 'Error reading suppressions at %s!\n%s' % (
260 self._happened_at, self._message)
263 def ReadValgrindStyleSuppressions(lines, supp_descriptor):
264 """Given a list of lines, returns a list of suppressions.
266 Args:
267 lines: a list of lines containing suppressions.
268 supp_descriptor: should typically be a filename.
269 Used only when printing errors.
271 result = []
272 cur_descr = ''
273 cur_type = ''
274 cur_stack = []
275 in_suppression = False
276 nline = 0
277 for line in lines:
278 nline += 1
279 line = line.strip()
280 if line.startswith('#'):
281 continue
282 if not in_suppression:
283 if not line:
284 # empty lines between suppressions
285 pass
286 elif line.startswith('{'):
287 in_suppression = True
288 pass
289 else:
290 raise SuppressionError('Expected: "{"',
291 "%s:%d" % (supp_descriptor, nline))
292 elif line.startswith('}'):
293 result.append(
294 ValgrindStyleSuppression(cur_descr, cur_type, cur_stack,
295 "%s:%d" % (supp_descriptor, nline)))
296 cur_descr = ''
297 cur_type = ''
298 cur_stack = []
299 in_suppression = False
300 elif not cur_descr:
301 cur_descr = line
302 continue
303 elif not cur_type:
304 if (not line.startswith("Memcheck:") and
305 not line.startswith("ThreadSanitizer:") and
306 (line != "Heapcheck:Leak")):
307 raise SuppressionError(
308 'Expected "Memcheck:TYPE", "ThreadSanitizer:TYPE" '
309 'or "Heapcheck:Leak", got "%s"' % line,
310 "%s:%d" % (supp_descriptor, nline))
311 supp_type = line.split(':')[1]
312 if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8",
313 "Cond", "Free", "Jump", "Leak", "Overlap", "Param",
314 "Value1", "Value2", "Value4", "Value8",
315 "Race", "UnlockNonLocked", "InvalidLock",
316 "Unaddressable", "Uninitialized"]:
317 raise SuppressionError('Unknown suppression type "%s"' % supp_type,
318 "%s:%d" % (supp_descriptor, nline))
319 cur_type = line
320 continue
321 elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line):
322 cur_stack.append(line.strip())
323 elif len(cur_stack) == 0 and cur_type == "Memcheck:Param":
324 cur_stack.append(line.strip())
325 else:
326 raise SuppressionError(
327 '"fun:function_name" or "obj:object_file" or "..." expected',
328 "%s:%d" % (supp_descriptor, nline))
329 return result
332 def PresubmitCheckSuppressions(supps):
333 """Check a list of suppressions and return a list of SuppressionErrors.
335 Mostly useful for separating the checking logic from the Presubmit API for
336 testing.
338 known_supp_names = {} # Key: name, Value: suppression.
339 errors = []
340 for s in supps:
341 if re.search("<.*suppression.name.here>", s.description):
342 # Suppression name line is
343 # <insert_a_suppression_name_here> for Memcheck,
344 # <Put your suppression name here> for TSan,
345 # name=<insert_a_suppression_name_here> for DrMemory
346 errors.append(
347 SuppressionError(
348 "You've forgotten to put a suppression name like bug_XXX",
349 s.defined_at))
350 continue
352 if s.description in known_supp_names:
353 errors.append(
354 SuppressionError(
355 'Suppression named "%s" is defined more than once, '
356 'see %s' % (s.description,
357 known_supp_names[s.description].defined_at),
358 s.defined_at))
359 else:
360 known_supp_names[s.description] = s
361 return errors
364 def PresubmitCheck(input_api, output_api):
365 """A helper function useful in PRESUBMIT.py
366 Returns a list of errors or [].
368 sup_regex = re.compile('suppressions.*\.txt$')
369 filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles()
370 if sup_regex.search(f.LocalPath())]
372 errors = []
374 # TODO(timurrrr): warn on putting suppressions into a wrong file,
375 # e.g. TSan suppression in a memcheck file.
377 for f in filenames:
378 try:
379 supps = ReadSuppressionsFromFile(f)
380 errors.extend(PresubmitCheckSuppressions(supps))
381 except SuppressionError as e:
382 errors.append(e)
384 return [output_api.PresubmitError(str(e)) for e in errors]
387 class DrMemorySuppression(Suppression):
388 """A suppression using the DrMemory syntax.
390 Attributes:
391 instr: The instruction to match.
392 Rest inherited from Suppression.
395 def __init__(self, name, report_type, instr, stack, defined_at):
396 """Constructor."""
397 self.instr = instr
399 # Construct the regex.
400 regex = '{\n'
401 if report_type == 'LEAK':
402 regex += '(POSSIBLE )?LEAK'
403 else:
404 regex += report_type
405 regex += '\nname=.*\n'
407 # TODO(rnk): Implement http://crbug.com/107416#c5 .
408 # drmemory_analyze.py doesn't generate suppressions with an instruction in
409 # them, so these suppressions will always fail to match. We should override
410 # Match to fetch the instruction from the report and try to match against
411 # that.
412 if instr:
413 regex += 'instruction=%s\n' % GlobToRegex(instr)
415 for line in stack:
416 if line == ELLIPSIS:
417 regex += '(.*\n)*'
418 elif '!' in line:
419 (mod, func) = line.split('!')
420 if func == ELLIPSIS: # mod!ellipsis frame
421 regex += '(%s\!.*\n)+' % GlobToRegex(mod, ignore_case=True)
422 else: # mod!func frame
423 # Ignore case for the module match, but not the function match.
424 regex += '%s\!%s\n' % (GlobToRegex(mod, ignore_case=True),
425 GlobToRegex(func, ignore_case=False))
426 else:
427 regex += GlobToRegex(line)
428 regex += '\n'
429 regex += '(.*\n)*' # Match anything left in the stack.
430 regex += '}'
431 return super(DrMemorySuppression, self).__init__(name, report_type, stack,
432 defined_at, regex)
434 def __str__(self):
435 """Stringify."""
436 text = self.type + "\n"
437 if self.description:
438 text += "name=%s\n" % self.description
439 if self.instr:
440 text += "instruction=%s\n" % self.instr
441 text += "\n".join(self.stack)
442 text += "\n"
443 return text
446 # Possible DrMemory error report types. Keep consistent with suppress_name
447 # array in drmemory/drmemory/report.c.
448 DRMEMORY_ERROR_TYPES = [
449 'UNADDRESSABLE ACCESS',
450 'UNINITIALIZED READ',
451 'INVALID HEAP ARGUMENT',
452 'GDI USAGE ERROR',
453 'HANDLE LEAK',
454 'LEAK',
455 'POSSIBLE LEAK',
456 'WARNING',
460 # Regexes to match valid drmemory frames.
461 DRMEMORY_FRAME_PATTERNS = [
462 re.compile(r"^.*\!.*$"), # mod!func
463 re.compile(r"^.*!\.\.\.$"), # mod!ellipsis
464 re.compile(r"^\<.*\+0x.*\>$"), # <mod+0xoffs>
465 re.compile(r"^\<not in a module\>$"),
466 re.compile(r"^system call .*$"),
467 re.compile(r"^\*$"), # wildcard
468 re.compile(r"^\.\.\.$"), # ellipsis
472 def ReadDrMemorySuppressions(lines, supp_descriptor):
473 """Given a list of lines, returns a list of DrMemory suppressions.
475 Args:
476 lines: a list of lines containing suppressions.
477 supp_descriptor: should typically be a filename.
478 Used only when parsing errors happen.
480 lines = StripAndSkipCommentsIterator(lines)
481 suppressions = []
482 for (line_no, line) in lines:
483 if not line:
484 continue
485 if line not in DRMEMORY_ERROR_TYPES:
486 raise SuppressionError('Expected a DrMemory error type, '
487 'found %r instead\n Valid error types: %s' %
488 (line, ' '.join(DRMEMORY_ERROR_TYPES)),
489 "%s:%d" % (supp_descriptor, line_no))
491 # Suppression starts here.
492 report_type = line
493 name = ''
494 instr = None
495 stack = []
496 defined_at = "%s:%d" % (supp_descriptor, line_no)
497 found_stack = False
498 for (line_no, line) in lines:
499 if not found_stack and line.startswith('name='):
500 name = line.replace('name=', '')
501 elif not found_stack and line.startswith('instruction='):
502 instr = line.replace('instruction=', '')
503 else:
504 # Unrecognized prefix indicates start of stack trace.
505 found_stack = True
506 if not line:
507 # Blank line means end of suppression.
508 break
509 if not any([regex.match(line) for regex in DRMEMORY_FRAME_PATTERNS]):
510 raise SuppressionError(
511 ('Unexpected stack frame pattern at line %d\n' +
512 'Frames should be one of the following:\n' +
513 ' module!function\n' +
514 ' module!...\n' +
515 ' <module+0xhexoffset>\n' +
516 ' <not in a module>\n' +
517 ' system call Name\n' +
518 ' *\n' +
519 ' ...\n') % line_no, defined_at)
520 stack.append(line)
522 if len(stack) == 0: # In case we hit EOF or blank without any stack frames.
523 raise SuppressionError('Suppression "%s" has no stack frames, ends at %d'
524 % (name, line_no), defined_at)
525 if stack[-1] == ELLIPSIS:
526 raise SuppressionError('Suppression "%s" ends in an ellipsis on line %d' %
527 (name, line_no), defined_at)
529 suppressions.append(
530 DrMemorySuppression(name, report_type, instr, stack, defined_at))
532 return suppressions
535 def ParseSuppressionOfType(lines, supp_descriptor, def_line_no, report_type):
536 """Parse the suppression starting on this line.
538 Suppressions start with a type, have an optional name and instruction, and a
539 stack trace that ends in a blank line.
544 def TestStack(stack, positive, negative, suppression_parser=None):
545 """A helper function for SelfTest() that checks a single stack.
547 Args:
548 stack: the stack to match the suppressions.
549 positive: the list of suppressions that must match the given stack.
550 negative: the list of suppressions that should not match.
551 suppression_parser: optional arg for the suppression parser, default is
552 ReadValgrindStyleSuppressions.
554 if not suppression_parser:
555 suppression_parser = ReadValgrindStyleSuppressions
556 for supp in positive:
557 parsed = suppression_parser(supp.split("\n"), "positive_suppression")
558 assert parsed[0].Match(stack.split("\n")), (
559 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack))
560 for supp in negative:
561 parsed = suppression_parser(supp.split("\n"), "negative_suppression")
562 assert not parsed[0].Match(stack.split("\n")), (
563 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack))
566 def TestFailPresubmit(supp_text, error_text, suppression_parser=None):
567 """A helper function for SelfTest() that verifies a presubmit check fires.
569 Args:
570 supp_text: suppression text to parse.
571 error_text: text of the presubmit error we expect to find.
572 suppression_parser: optional arg for the suppression parser, default is
573 ReadValgrindStyleSuppressions.
575 if not suppression_parser:
576 suppression_parser = ReadValgrindStyleSuppressions
577 try:
578 supps = suppression_parser(supp_text.split("\n"), "<presubmit suppression>")
579 except SuppressionError, e:
580 # If parsing raised an exception, match the error text here.
581 assert error_text in str(e), (
582 "presubmit text %r not in SuppressionError:\n%r" %
583 (error_text, str(e)))
584 else:
585 # Otherwise, run the presubmit checks over the supps. We expect a single
586 # error that has text matching error_text.
587 errors = PresubmitCheckSuppressions(supps)
588 assert len(errors) == 1, (
589 "expected exactly one presubmit error, got:\n%s" % errors)
590 assert error_text in str(errors[0]), (
591 "presubmit text %r not in SuppressionError:\n%r" %
592 (error_text, str(errors[0])))
595 def SelfTest():
596 """Tests the Suppression.Match() capabilities."""
598 test_memcheck_stack_1 = """{
599 test
600 Memcheck:Leak
601 fun:absolutly
602 fun:brilliant
603 obj:condition
604 fun:detection
605 fun:expression
606 }"""
608 test_memcheck_stack_2 = """{
609 test
610 Memcheck:Uninitialized
611 fun:absolutly
612 fun:brilliant
613 obj:condition
614 fun:detection
615 fun:expression
616 }"""
618 test_memcheck_stack_3 = """{
619 test
620 Memcheck:Unaddressable
621 fun:absolutly
622 fun:brilliant
623 obj:condition
624 fun:detection
625 fun:expression
626 }"""
628 test_memcheck_stack_4 = """{
629 test
630 Memcheck:Addr4
631 fun:absolutly
632 fun:brilliant
633 obj:condition
634 fun:detection
635 fun:expression
636 }"""
638 test_heapcheck_stack = """{
639 test
640 Heapcheck:Leak
641 fun:absolutly
642 fun:brilliant
643 obj:condition
644 fun:detection
645 fun:expression
646 }"""
648 test_tsan_stack = """{
649 test
650 ThreadSanitizer:Race
651 fun:absolutly
652 fun:brilliant
653 obj:condition
654 fun:detection
655 fun:expression
656 }"""
659 positive_memcheck_suppressions_1 = [
660 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
661 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n}",
662 "{\nzzz\nMemcheck:Leak\nfun:absolutly\nfun:brilliant\n}",
663 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\n}",
664 "{\nzzz\nMemcheck:Leak\n...\nfun:detection\n}",
665 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n...\nfun:detection\n}",
666 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n...\nfun:detection\n}",
667 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\n}",
668 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\nfun:detection\n}",
669 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\nobj:condition\n}",
672 positive_memcheck_suppressions_2 = [
673 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
674 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*ly\n}",
675 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\nfun:brilliant\n}",
676 # Legacy suppression types
677 "{\nzzz\nMemcheck:Value1\n...\nfun:brilliant\n}",
678 "{\nzzz\nMemcheck:Cond\n...\nfun:detection\n}",
679 "{\nzzz\nMemcheck:Value8\nfun:absolutly\nfun:brilliant\n}",
682 positive_memcheck_suppressions_3 = [
683 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
684 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
685 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
686 # Legacy suppression types
687 "{\nzzz\nMemcheck:Addr1\n...\nfun:brilliant\n}",
688 "{\nzzz\nMemcheck:Addr8\n...\nfun:detection\n}",
691 positive_memcheck_suppressions_4 = [
692 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\n}",
693 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
694 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\nfun:brilliant\n}",
695 "{\nzzz\nMemcheck:Unaddressable\n...\nfun:brilliant\n}",
696 "{\nzzz\nMemcheck:Addr4\n...\nfun:detection\n}",
699 positive_heapcheck_suppressions = [
700 "{\nzzz\nHeapcheck:Leak\n...\nobj:condition\n}",
701 "{\nzzz\nHeapcheck:Leak\nfun:absolutly\n}",
704 positive_tsan_suppressions = [
705 "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}",
706 "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}",
709 negative_memcheck_suppressions_1 = [
710 "{\nzzz\nMemcheck:Leak\nfun:abnormal\n}",
711 "{\nzzz\nMemcheck:Leak\nfun:ab*liant\n}",
712 "{\nzzz\nMemcheck:Leak\nfun:brilliant\n}",
713 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
714 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
717 negative_memcheck_suppressions_2 = [
718 "{\nzzz\nMemcheck:Cond\nfun:abnormal\n}",
719 "{\nzzz\nMemcheck:Value2\nfun:abnormal\n}",
720 "{\nzzz\nMemcheck:Uninitialized\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}",
724 "{\nzzz\nMemcheck:Unaddressable\nfun:brilliant\n}",
727 negative_memcheck_suppressions_3 = [
728 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
729 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
730 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
731 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
732 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
733 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
736 negative_memcheck_suppressions_4 = [
737 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
738 "{\nzzz\nMemcheck:Addr4\nfun:abnormal\n}",
739 "{\nzzz\nMemcheck:Unaddressable\nfun:abnormal\n}",
740 "{\nzzz\nMemcheck:Addr1\nfun:absolutly\n}",
741 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
742 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
743 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
744 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
747 negative_heapcheck_suppressions = [
748 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
749 "{\nzzz\nHeapcheck:Leak\nfun:brilliant\n}",
752 negative_tsan_suppressions = [
753 "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}",
754 "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}",
757 TestStack(test_memcheck_stack_1,
758 positive_memcheck_suppressions_1,
759 negative_memcheck_suppressions_1)
760 TestStack(test_memcheck_stack_2,
761 positive_memcheck_suppressions_2,
762 negative_memcheck_suppressions_2)
763 TestStack(test_memcheck_stack_3,
764 positive_memcheck_suppressions_3,
765 negative_memcheck_suppressions_3)
766 TestStack(test_memcheck_stack_4,
767 positive_memcheck_suppressions_4,
768 negative_memcheck_suppressions_4)
769 TestStack(test_heapcheck_stack, positive_heapcheck_suppressions,
770 negative_heapcheck_suppressions)
771 TestStack(test_tsan_stack, positive_tsan_suppressions,
772 negative_tsan_suppressions)
774 # TODO(timurrrr): add TestFailPresubmit tests.
776 ### DrMemory self tests.
778 # http://crbug.com/96010 suppression.
779 stack_96010 = """{
780 UNADDRESSABLE ACCESS
781 name=<insert_a_suppression_name_here>
782 *!TestingProfile::FinishInit
783 *!TestingProfile::TestingProfile
784 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
785 *!testing::Test::Run
786 }"""
788 suppress_96010 = [
789 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n",
790 ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" +
791 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"),
792 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n",
793 "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n",
794 # No name should be needed
795 "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n",
796 # Whole trace
797 ("UNADDRESSABLE ACCESS\n" +
798 "*!TestingProfile::FinishInit\n" +
799 "*!TestingProfile::TestingProfile\n" +
800 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
801 "*!testing::Test::Run\n"),
804 negative_96010 = [
805 # Wrong type
806 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
807 # No ellipsis
808 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
811 TestStack(stack_96010, suppress_96010, negative_96010,
812 suppression_parser=ReadDrMemorySuppressions)
814 # Invalid heap arg
815 stack_invalid = """{
816 INVALID HEAP ARGUMENT
817 name=asdf
818 *!foo
819 }"""
820 suppress_invalid = [
821 "INVALID HEAP ARGUMENT\n*!foo\n",
823 negative_invalid = [
824 "UNADDRESSABLE ACCESS\n*!foo\n",
827 TestStack(stack_invalid, suppress_invalid, negative_invalid,
828 suppression_parser=ReadDrMemorySuppressions)
830 # Suppress only ntdll
831 stack_in_ntdll = """{
832 UNADDRESSABLE ACCESS
833 name=<insert_a_suppression_name_here>
834 ntdll.dll!RtlTryEnterCriticalSection
835 }"""
836 stack_not_ntdll = """{
837 UNADDRESSABLE ACCESS
838 name=<insert_a_suppression_name_here>
839 notntdll.dll!RtlTryEnterCriticalSection
840 }"""
842 suppress_in_ntdll = [
843 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
845 suppress_in_any = [
846 "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
849 TestStack(stack_in_ntdll, suppress_in_ntdll + suppress_in_any, [],
850 suppression_parser=ReadDrMemorySuppressions)
851 # Make sure we don't wildcard away the "not" part and match ntdll.dll by
852 # accident.
853 TestStack(stack_not_ntdll, suppress_in_any, suppress_in_ntdll,
854 suppression_parser=ReadDrMemorySuppressions)
856 # Suppress a POSSIBLE LEAK with LEAK.
857 stack_foo_possible = """{
858 POSSIBLE LEAK
859 name=foo possible
860 *!foo
861 }"""
862 suppress_foo_possible = [ "POSSIBLE LEAK\n*!foo\n" ]
863 suppress_foo_leak = [ "LEAK\n*!foo\n" ]
864 TestStack(stack_foo_possible, suppress_foo_possible + suppress_foo_leak, [],
865 suppression_parser=ReadDrMemorySuppressions)
867 # Don't suppress LEAK with POSSIBLE LEAK.
868 stack_foo_leak = """{
869 LEAK
870 name=foo leak
871 *!foo
872 }"""
873 TestStack(stack_foo_leak, suppress_foo_leak, suppress_foo_possible,
874 suppression_parser=ReadDrMemorySuppressions)
876 # Test case insensitivity of module names.
877 stack_user32_mixed_case = """{
878 LEAK
879 name=<insert>
880 USER32.dll!foo
881 user32.DLL!bar
882 user32.dll!baz
883 }"""
884 suppress_user32 = [ # Module name case doesn't matter.
885 "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n",
886 "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n",
888 no_suppress_user32 = [ # Function name case matters.
889 "LEAK\nuser32.dll!FOO\nuser32.dll!BAR\nuser32.dll!BAZ\n",
890 "LEAK\nUSER32.DLL!FOO\nUSER32.DLL!BAR\nUSER32.DLL!BAZ\n",
892 TestStack(stack_user32_mixed_case, suppress_user32, no_suppress_user32,
893 suppression_parser=ReadDrMemorySuppressions)
895 # Test mod!... frames.
896 stack_kernel32_through_ntdll = """{
897 LEAK
898 name=<insert>
899 kernel32.dll!foo
900 KERNEL32.dll!bar
901 kernel32.DLL!baz
902 ntdll.dll!quux
903 }"""
904 suppress_mod_ellipsis = [
905 "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
906 "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
908 no_suppress_mod_ellipsis = [
909 # Need one or more matching frames, not zero, unlike regular ellipsis.
910 "LEAK\nuser32.dll!...\nkernel32.dll!...\nntdll.dll!quux\n",
912 TestStack(stack_kernel32_through_ntdll, suppress_mod_ellipsis,
913 no_suppress_mod_ellipsis,
914 suppression_parser=ReadDrMemorySuppressions)
916 # Test that the presubmit checks work.
917 forgot_to_name = """
918 UNADDRESSABLE ACCESS
919 name=<insert_a_suppression_name_here>
920 ntdll.dll!RtlTryEnterCriticalSection
922 TestFailPresubmit(forgot_to_name, 'forgotten to put a suppression',
923 suppression_parser=ReadDrMemorySuppressions)
925 named_twice = """
926 UNADDRESSABLE ACCESS
927 name=http://crbug.com/1234
928 *!foo
930 UNADDRESSABLE ACCESS
931 name=http://crbug.com/1234
932 *!bar
934 TestFailPresubmit(named_twice, 'defined more than once',
935 suppression_parser=ReadDrMemorySuppressions)
937 forgot_stack = """
938 UNADDRESSABLE ACCESS
939 name=http://crbug.com/1234
941 TestFailPresubmit(forgot_stack, 'has no stack frames',
942 suppression_parser=ReadDrMemorySuppressions)
944 ends_in_ellipsis = """
945 UNADDRESSABLE ACCESS
946 name=http://crbug.com/1234
947 ntdll.dll!RtlTryEnterCriticalSection
950 TestFailPresubmit(ends_in_ellipsis, 'ends in an ellipsis',
951 suppression_parser=ReadDrMemorySuppressions)
953 bad_stack_frame = """
954 UNADDRESSABLE ACCESS
955 name=http://crbug.com/1234
956 fun:memcheck_style_frame
958 TestFailPresubmit(bad_stack_frame, 'Unexpected stack frame pattern',
959 suppression_parser=ReadDrMemorySuppressions)
961 # Test FilenameToTool.
962 filenames_to_tools = {
963 "tools/heapcheck/suppressions.txt": "heapcheck",
964 "tools/valgrind/tsan/suppressions.txt": "tsan",
965 "tools/valgrind/drmemory/suppressions.txt": "drmemory",
966 "tools/valgrind/drmemory/suppressions_full.txt": "drmemory",
967 "tools/valgrind/memcheck/suppressions.txt": "memcheck",
968 "tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
969 "asdf/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
970 "foo/bar/baz/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
971 "foo/bar/baz/tools/valgrind/suppressions.txt": None,
972 "tools/valgrind/suppressions.txt": None,
974 for (filename, expected_tool) in filenames_to_tools.items():
975 filename.replace('/', os.sep) # Make the path look native.
976 tool = FilenameToTool(filename)
977 assert tool == expected_tool, (
978 "failed to get expected tool for filename %r, expected %s, got %s" %
979 (filename, expected_tool, tool))
981 # Test ValgrindStyleSuppression.__str__.
982 supp = ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
983 ["...", "fun:foo"], "supp.txt:1")
984 # Intentional 3-space indent. =/
985 supp_str = ("{\n"
986 " http://crbug.com/1234\n"
987 " Memcheck:Leak\n"
988 " ...\n"
989 " fun:foo\n"
990 "}\n")
991 assert str(supp) == supp_str, (
992 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
994 # Test DrMemorySuppression.__str__.
995 supp = DrMemorySuppression(
996 "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
997 supp_str = ("LEAK\n"
998 "name=http://crbug.com/1234\n"
999 "...\n"
1000 "*!foo\n")
1001 assert str(supp) == supp_str, (
1002 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
1004 supp = DrMemorySuppression(
1005 "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01",
1006 ["ntdll.dll!*", "*!foo"], "supp.txt:1")
1007 supp_str = ("UNINITIALIZED READ\n"
1008 "name=http://crbug.com/1234\n"
1009 "instruction=test 0x08(%eax) $0x01\n"
1010 "ntdll.dll!*\n"
1011 "*!foo\n")
1012 assert str(supp) == supp_str, (
1013 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
1016 if __name__ == '__main__':
1017 SelfTest()
1018 print 'PASS'