Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / valgrind / suppressions.py
blobe066bedead15e70198e3b8676862c93df4315b68
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 result['common_suppressions'] = vg_common
50 supp_filename = JOIN(suppressions_root, "memcheck", "suppressions_linux.txt")
51 vg_linux = ReadSuppressionsFromFile(supp_filename)
52 result['linux_suppressions'] = vg_linux
54 supp_filename = JOIN(suppressions_root, "memcheck", "suppressions_mac.txt")
55 vg_mac = ReadSuppressionsFromFile(supp_filename)
56 result['mac_suppressions'] = vg_mac
58 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions.txt")
59 result['drmem_suppressions'] = ReadSuppressionsFromFile(supp_filename)
60 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions_full.txt")
61 result['drmem_full_suppressions'] = ReadSuppressionsFromFile(supp_filename)
63 return result
66 def GlobToRegex(glob_pattern, ignore_case=False):
67 """Translate glob wildcards (*?) into regex syntax. Escape the rest."""
68 regex = ''
69 for char in glob_pattern:
70 if char == '*':
71 regex += '.*'
72 elif char == '?':
73 regex += '.'
74 elif ignore_case and char.isalpha():
75 regex += '[%s%s]' % (char.lower(), char.upper())
76 else:
77 regex += re.escape(char)
78 return ''.join(regex)
81 def StripAndSkipCommentsIterator(lines):
82 """Generator of (line_no, line) pairs that strips comments and whitespace."""
83 for (line_no, line) in enumerate(lines):
84 line = line.strip() # Drop \n
85 if line.startswith('#'):
86 continue # Comments
87 # Skip comment lines, but not empty lines, they indicate the end of a
88 # suppression. Add one to the line number as well, since most editors use
89 # 1-based numberings, and enumerate is 0-based.
90 yield (line_no + 1, line)
93 class Suppression(object):
94 """This class represents a single stack trace suppression.
96 Attributes:
97 description: A string representing the error description.
98 type: A string representing the error type, e.g. Memcheck:Leak.
99 stack: The lines comprising the stack trace for the suppression.
100 regex: The actual regex used to match against scraped reports.
103 def __init__(self, description, type, stack, defined_at, regex):
104 """Inits Suppression.
106 description, type, stack, regex: same as class attributes
107 defined_at: file:line identifying where the suppression was defined
109 self.description = description
110 self.type = type
111 self.stack = stack
112 self.defined_at = defined_at
113 self.regex = re.compile(regex, re.MULTILINE)
115 def Match(self, suppression_from_report):
116 """Returns bool indicating whether this suppression matches
117 the suppression generated from Valgrind error report.
119 We match our suppressions against generated suppressions
120 (not against reports) since they have the same format
121 while the reports are taken from XML, contain filenames,
122 they are demangled, and are generally more difficult to
123 parse.
125 Args:
126 suppression_from_report: list of strings (function names).
127 Returns:
128 True if the suppression is not empty and matches the report.
130 if not self.stack:
131 return False
132 lines = [f.strip() for f in suppression_from_report]
133 return self.regex.match('\n'.join(lines) + '\n') is not None
136 def FilenameToTool(filename):
137 """Return the name of the tool that a file is related to, or None.
139 Example mappings:
140 tools/valgrind/drmemory/suppressions.txt -> drmemory
141 tools/valgrind/drmemory/suppressions_full.txt -> drmemory
142 tools/valgrind/memcheck/suppressions.txt -> memcheck
143 tools/valgrind/memcheck/suppressions_mac.txt -> memcheck
145 filename = os.path.abspath(filename)
146 parts = filename.split(os.sep)
147 tool = parts[-2]
148 if tool in ('drmemory', 'memcheck'):
149 return tool
150 return None
153 def ReadSuppressionsFromFile(filename):
154 """Read suppressions from the given file and return them as a list"""
155 tool_to_parser = {
156 "drmemory": ReadDrMemorySuppressions,
157 "memcheck": ReadValgrindStyleSuppressions,
159 tool = FilenameToTool(filename)
160 assert tool in tool_to_parser, (
161 "unknown tool %s for filename %s" % (tool, filename))
162 parse_func = tool_to_parser[tool]
164 # Consider non-existent files to be empty.
165 if not os.path.exists(filename):
166 return []
168 input_file = file(filename, 'r')
169 try:
170 return parse_func(input_file, filename)
171 except SuppressionError:
172 input_file.close()
173 raise
176 class ValgrindStyleSuppression(Suppression):
177 """A suppression using the Valgrind syntax.
179 Most tools, even ones that are not Valgrind-based, use this syntax.
181 Attributes:
182 Same as Suppression.
185 def __init__(self, description, type, stack, defined_at):
186 """Creates a suppression using the Memcheck syntax."""
187 regex = '{\n.*\n%s\n' % type
188 for line in stack:
189 if line == ELLIPSIS:
190 regex += '(.*\n)*'
191 else:
192 regex += GlobToRegex(line)
193 regex += '\n'
194 regex += '(.*\n)*'
195 regex += '}'
197 # In the recent version of valgrind-variant we've switched
198 # from memcheck's default Addr[1248]/Value[1248]/Cond suppression types
199 # to simply Unaddressable/Uninitialized.
200 # The suppression generator no longer gives us "old" types thus
201 # for the "new-type" suppressions:
202 # * Memcheck:Unaddressable should also match Addr* reports,
203 # * Memcheck:Uninitialized should also match Cond and Value reports,
205 # We also want to support legacy suppressions (e.g. copied from
206 # upstream bugs etc), so:
207 # * Memcheck:Addr[1248] suppressions should match Unaddressable reports,
208 # * Memcheck:Cond and Memcheck:Value[1248] should match Uninitialized.
209 # Please note the latest two rules only apply to the
210 # tools/valgrind/waterfall.sh suppression matcher and the real
211 # valgrind-variant Memcheck will not suppress
212 # e.g. Addr1 printed as Unaddressable with Addr4 suppression.
213 # Be careful to check the access size while copying legacy suppressions!
214 for sz in [1, 2, 4, 8]:
215 regex = regex.replace("\nMemcheck:Addr%d\n" % sz,
216 "\nMemcheck:(Addr%d|Unaddressable)\n" % sz)
217 regex = regex.replace("\nMemcheck:Value%d\n" % sz,
218 "\nMemcheck:(Value%d|Uninitialized)\n" % sz)
219 regex = regex.replace("\nMemcheck:Cond\n",
220 "\nMemcheck:(Cond|Uninitialized)\n")
221 regex = regex.replace("\nMemcheck:Unaddressable\n",
222 "\nMemcheck:(Addr.|Unaddressable)\n")
223 regex = regex.replace("\nMemcheck:Uninitialized\n",
224 "\nMemcheck:(Cond|Value.|Uninitialized)\n")
226 return super(ValgrindStyleSuppression, self).__init__(
227 description, type, stack, defined_at, regex)
229 def __str__(self):
230 """Stringify."""
231 lines = [self.description, self.type] + self.stack
232 return "{\n %s\n}\n" % "\n ".join(lines)
235 class SuppressionError(Exception):
236 def __init__(self, message, happened_at):
237 self._message = message
238 self._happened_at = happened_at
240 def __str__(self):
241 return 'Error reading suppressions at %s!\n%s' % (
242 self._happened_at, self._message)
245 def ReadValgrindStyleSuppressions(lines, supp_descriptor):
246 """Given a list of lines, returns a list of suppressions.
248 Args:
249 lines: a list of lines containing suppressions.
250 supp_descriptor: should typically be a filename.
251 Used only when printing errors.
253 result = []
254 cur_descr = ''
255 cur_type = ''
256 cur_stack = []
257 in_suppression = False
258 nline = 0
259 for line in lines:
260 nline += 1
261 line = line.strip()
262 if line.startswith('#'):
263 continue
264 if not in_suppression:
265 if not line:
266 # empty lines between suppressions
267 pass
268 elif line.startswith('{'):
269 in_suppression = True
270 pass
271 else:
272 raise SuppressionError('Expected: "{"',
273 "%s:%d" % (supp_descriptor, nline))
274 elif line.startswith('}'):
275 result.append(
276 ValgrindStyleSuppression(cur_descr, cur_type, cur_stack,
277 "%s:%d" % (supp_descriptor, nline)))
278 cur_descr = ''
279 cur_type = ''
280 cur_stack = []
281 in_suppression = False
282 elif not cur_descr:
283 cur_descr = line
284 continue
285 elif not cur_type:
286 if not line.startswith("Memcheck:"):
287 raise SuppressionError(
288 'Expected "Memcheck:TYPE", got "%s"' % line,
289 "%s:%d" % (supp_descriptor, nline))
290 supp_type = line.split(':')[1]
291 if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8",
292 "Cond", "Free", "Jump", "Leak", "Overlap", "Param",
293 "Value1", "Value2", "Value4", "Value8",
294 "Unaddressable", "Uninitialized"]:
295 raise SuppressionError('Unknown suppression type "%s"' % supp_type,
296 "%s:%d" % (supp_descriptor, nline))
297 cur_type = line
298 continue
299 elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line):
300 cur_stack.append(line.strip())
301 elif len(cur_stack) == 0 and cur_type == "Memcheck:Param":
302 cur_stack.append(line.strip())
303 else:
304 raise SuppressionError(
305 '"fun:function_name" or "obj:object_file" or "..." expected',
306 "%s:%d" % (supp_descriptor, nline))
307 return result
310 def PresubmitCheckSuppressions(supps):
311 """Check a list of suppressions and return a list of SuppressionErrors.
313 Mostly useful for separating the checking logic from the Presubmit API for
314 testing.
316 known_supp_names = {} # Key: name, Value: suppression.
317 errors = []
318 for s in supps:
319 if re.search("<.*suppression.name.here>", s.description):
320 # Suppression name line is
321 # <insert_a_suppression_name_here> for Memcheck,
322 # name=<insert_a_suppression_name_here> for DrMemory
323 errors.append(
324 SuppressionError(
325 "You've forgotten to put a suppression name like bug_XXX",
326 s.defined_at))
327 continue
329 if s.description in known_supp_names:
330 errors.append(
331 SuppressionError(
332 'Suppression named "%s" is defined more than once, '
333 'see %s' % (s.description,
334 known_supp_names[s.description].defined_at),
335 s.defined_at))
336 else:
337 known_supp_names[s.description] = s
338 return errors
341 def PresubmitCheck(input_api, output_api):
342 """A helper function useful in PRESUBMIT.py
343 Returns a list of errors or [].
345 sup_regex = re.compile('suppressions.*\.txt$')
346 filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles()
347 if sup_regex.search(f.LocalPath())]
349 errors = []
351 for f in filenames:
352 try:
353 supps = ReadSuppressionsFromFile(f)
354 errors.extend(PresubmitCheckSuppressions(supps))
355 except SuppressionError as e:
356 errors.append(e)
358 return [output_api.PresubmitError(str(e)) for e in errors]
361 class DrMemorySuppression(Suppression):
362 """A suppression using the DrMemory syntax.
364 Attributes:
365 instr: The instruction to match.
366 Rest inherited from Suppression.
369 def __init__(self, name, report_type, instr, stack, defined_at):
370 """Constructor."""
371 self.instr = instr
373 # Construct the regex.
374 regex = '{\n'
375 if report_type == 'LEAK':
376 regex += '(POSSIBLE )?LEAK'
377 else:
378 regex += report_type
379 regex += '\nname=.*\n'
381 # TODO(rnk): Implement http://crbug.com/107416#c5 .
382 # drmemory_analyze.py doesn't generate suppressions with an instruction in
383 # them, so these suppressions will always fail to match. We should override
384 # Match to fetch the instruction from the report and try to match against
385 # that.
386 if instr:
387 regex += 'instruction=%s\n' % GlobToRegex(instr)
389 for line in stack:
390 if line == ELLIPSIS:
391 regex += '(.*\n)*'
392 elif '!' in line:
393 (mod, func) = line.split('!')
394 if func == ELLIPSIS: # mod!ellipsis frame
395 regex += '(%s\!.*\n)+' % GlobToRegex(mod, ignore_case=True)
396 else: # mod!func frame
397 # Ignore case for the module match, but not the function match.
398 regex += '%s\!%s\n' % (GlobToRegex(mod, ignore_case=True),
399 GlobToRegex(func, ignore_case=False))
400 else:
401 regex += GlobToRegex(line)
402 regex += '\n'
403 regex += '(.*\n)*' # Match anything left in the stack.
404 regex += '}'
405 return super(DrMemorySuppression, self).__init__(name, report_type, stack,
406 defined_at, regex)
408 def __str__(self):
409 """Stringify."""
410 text = self.type + "\n"
411 if self.description:
412 text += "name=%s\n" % self.description
413 if self.instr:
414 text += "instruction=%s\n" % self.instr
415 text += "\n".join(self.stack)
416 text += "\n"
417 return text
420 # Possible DrMemory error report types. Keep consistent with suppress_name
421 # array in drmemory/drmemory/report.c.
422 DRMEMORY_ERROR_TYPES = [
423 'UNADDRESSABLE ACCESS',
424 'UNINITIALIZED READ',
425 'INVALID HEAP ARGUMENT',
426 'GDI USAGE ERROR',
427 'HANDLE LEAK',
428 'LEAK',
429 'POSSIBLE LEAK',
430 'WARNING',
434 # Regexes to match valid drmemory frames.
435 DRMEMORY_FRAME_PATTERNS = [
436 re.compile(r"^.*\!.*$"), # mod!func
437 re.compile(r"^.*!\.\.\.$"), # mod!ellipsis
438 re.compile(r"^\<.*\+0x.*\>$"), # <mod+0xoffs>
439 re.compile(r"^\<not in a module\>$"),
440 re.compile(r"^system call .*$"),
441 re.compile(r"^\*$"), # wildcard
442 re.compile(r"^\.\.\.$"), # ellipsis
446 def ReadDrMemorySuppressions(lines, supp_descriptor):
447 """Given a list of lines, returns a list of DrMemory suppressions.
449 Args:
450 lines: a list of lines containing suppressions.
451 supp_descriptor: should typically be a filename.
452 Used only when parsing errors happen.
454 lines = StripAndSkipCommentsIterator(lines)
455 suppressions = []
456 for (line_no, line) in lines:
457 if not line:
458 continue
459 if line not in DRMEMORY_ERROR_TYPES:
460 raise SuppressionError('Expected a DrMemory error type, '
461 'found %r instead\n Valid error types: %s' %
462 (line, ' '.join(DRMEMORY_ERROR_TYPES)),
463 "%s:%d" % (supp_descriptor, line_no))
465 # Suppression starts here.
466 report_type = line
467 name = ''
468 instr = None
469 stack = []
470 defined_at = "%s:%d" % (supp_descriptor, line_no)
471 found_stack = False
472 for (line_no, line) in lines:
473 if not found_stack and line.startswith('name='):
474 name = line.replace('name=', '')
475 elif not found_stack and line.startswith('instruction='):
476 instr = line.replace('instruction=', '')
477 else:
478 # Unrecognized prefix indicates start of stack trace.
479 found_stack = True
480 if not line:
481 # Blank line means end of suppression.
482 break
483 if not any([regex.match(line) for regex in DRMEMORY_FRAME_PATTERNS]):
484 raise SuppressionError(
485 ('Unexpected stack frame pattern at line %d\n' +
486 'Frames should be one of the following:\n' +
487 ' module!function\n' +
488 ' module!...\n' +
489 ' <module+0xhexoffset>\n' +
490 ' <not in a module>\n' +
491 ' system call Name\n' +
492 ' *\n' +
493 ' ...\n') % line_no, defined_at)
494 stack.append(line)
496 if len(stack) == 0: # In case we hit EOF or blank without any stack frames.
497 raise SuppressionError('Suppression "%s" has no stack frames, ends at %d'
498 % (name, line_no), defined_at)
499 if stack[-1] == ELLIPSIS:
500 raise SuppressionError('Suppression "%s" ends in an ellipsis on line %d' %
501 (name, line_no), defined_at)
503 suppressions.append(
504 DrMemorySuppression(name, report_type, instr, stack, defined_at))
506 return suppressions
509 def ParseSuppressionOfType(lines, supp_descriptor, def_line_no, report_type):
510 """Parse the suppression starting on this line.
512 Suppressions start with a type, have an optional name and instruction, and a
513 stack trace that ends in a blank line.
518 def TestStack(stack, positive, negative, suppression_parser=None):
519 """A helper function for SelfTest() that checks a single stack.
521 Args:
522 stack: the stack to match the suppressions.
523 positive: the list of suppressions that must match the given stack.
524 negative: the list of suppressions that should not match.
525 suppression_parser: optional arg for the suppression parser, default is
526 ReadValgrindStyleSuppressions.
528 if not suppression_parser:
529 suppression_parser = ReadValgrindStyleSuppressions
530 for supp in positive:
531 parsed = suppression_parser(supp.split("\n"), "positive_suppression")
532 assert parsed[0].Match(stack.split("\n")), (
533 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack))
534 for supp in negative:
535 parsed = suppression_parser(supp.split("\n"), "negative_suppression")
536 assert not parsed[0].Match(stack.split("\n")), (
537 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack))
540 def TestFailPresubmit(supp_text, error_text, suppression_parser=None):
541 """A helper function for SelfTest() that verifies a presubmit check fires.
543 Args:
544 supp_text: suppression text to parse.
545 error_text: text of the presubmit error we expect to find.
546 suppression_parser: optional arg for the suppression parser, default is
547 ReadValgrindStyleSuppressions.
549 if not suppression_parser:
550 suppression_parser = ReadValgrindStyleSuppressions
551 try:
552 supps = suppression_parser(supp_text.split("\n"), "<presubmit suppression>")
553 except SuppressionError, e:
554 # If parsing raised an exception, match the error text here.
555 assert error_text in str(e), (
556 "presubmit text %r not in SuppressionError:\n%r" %
557 (error_text, str(e)))
558 else:
559 # Otherwise, run the presubmit checks over the supps. We expect a single
560 # error that has text matching error_text.
561 errors = PresubmitCheckSuppressions(supps)
562 assert len(errors) == 1, (
563 "expected exactly one presubmit error, got:\n%s" % errors)
564 assert error_text in str(errors[0]), (
565 "presubmit text %r not in SuppressionError:\n%r" %
566 (error_text, str(errors[0])))
569 def SelfTest():
570 """Tests the Suppression.Match() capabilities."""
572 test_memcheck_stack_1 = """{
573 test
574 Memcheck:Leak
575 fun:absolutly
576 fun:brilliant
577 obj:condition
578 fun:detection
579 fun:expression
580 }"""
582 test_memcheck_stack_2 = """{
583 test
584 Memcheck:Uninitialized
585 fun:absolutly
586 fun:brilliant
587 obj:condition
588 fun:detection
589 fun:expression
590 }"""
592 test_memcheck_stack_3 = """{
593 test
594 Memcheck:Unaddressable
595 fun:absolutly
596 fun:brilliant
597 obj:condition
598 fun:detection
599 fun:expression
600 }"""
602 test_memcheck_stack_4 = """{
603 test
604 Memcheck:Addr4
605 fun:absolutly
606 fun:brilliant
607 obj:condition
608 fun:detection
609 fun:expression
610 }"""
612 positive_memcheck_suppressions_1 = [
613 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
614 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n}",
615 "{\nzzz\nMemcheck:Leak\nfun:absolutly\nfun:brilliant\n}",
616 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\n}",
617 "{\nzzz\nMemcheck:Leak\n...\nfun:detection\n}",
618 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n...\nfun:detection\n}",
619 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n...\nfun:detection\n}",
620 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\n}",
621 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\nfun:detection\n}",
622 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\nobj:condition\n}",
625 positive_memcheck_suppressions_2 = [
626 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
627 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*ly\n}",
628 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\nfun:brilliant\n}",
629 # Legacy suppression types
630 "{\nzzz\nMemcheck:Value1\n...\nfun:brilliant\n}",
631 "{\nzzz\nMemcheck:Cond\n...\nfun:detection\n}",
632 "{\nzzz\nMemcheck:Value8\nfun:absolutly\nfun:brilliant\n}",
635 positive_memcheck_suppressions_3 = [
636 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
637 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
638 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
639 # Legacy suppression types
640 "{\nzzz\nMemcheck:Addr1\n...\nfun:brilliant\n}",
641 "{\nzzz\nMemcheck:Addr8\n...\nfun:detection\n}",
644 positive_memcheck_suppressions_4 = [
645 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\n}",
646 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
647 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\nfun:brilliant\n}",
648 "{\nzzz\nMemcheck:Unaddressable\n...\nfun:brilliant\n}",
649 "{\nzzz\nMemcheck:Addr4\n...\nfun:detection\n}",
652 negative_memcheck_suppressions_1 = [
653 "{\nzzz\nMemcheck:Leak\nfun:abnormal\n}",
654 "{\nzzz\nMemcheck:Leak\nfun:ab*liant\n}",
655 "{\nzzz\nMemcheck:Leak\nfun:brilliant\n}",
656 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
657 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
660 negative_memcheck_suppressions_2 = [
661 "{\nzzz\nMemcheck:Cond\nfun:abnormal\n}",
662 "{\nzzz\nMemcheck:Value2\nfun:abnormal\n}",
663 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*liant\n}",
664 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
665 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
666 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
667 "{\nzzz\nMemcheck:Unaddressable\nfun:brilliant\n}",
670 negative_memcheck_suppressions_3 = [
671 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
672 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
673 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
674 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
675 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
676 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
679 negative_memcheck_suppressions_4 = [
680 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
681 "{\nzzz\nMemcheck:Addr4\nfun:abnormal\n}",
682 "{\nzzz\nMemcheck:Unaddressable\nfun:abnormal\n}",
683 "{\nzzz\nMemcheck:Addr1\nfun:absolutly\n}",
684 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
685 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
686 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
687 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
690 TestStack(test_memcheck_stack_1,
691 positive_memcheck_suppressions_1,
692 negative_memcheck_suppressions_1)
693 TestStack(test_memcheck_stack_2,
694 positive_memcheck_suppressions_2,
695 negative_memcheck_suppressions_2)
696 TestStack(test_memcheck_stack_3,
697 positive_memcheck_suppressions_3,
698 negative_memcheck_suppressions_3)
699 TestStack(test_memcheck_stack_4,
700 positive_memcheck_suppressions_4,
701 negative_memcheck_suppressions_4)
703 # TODO(timurrrr): add TestFailPresubmit tests.
705 ### DrMemory self tests.
707 # http://crbug.com/96010 suppression.
708 stack_96010 = """{
709 UNADDRESSABLE ACCESS
710 name=<insert_a_suppression_name_here>
711 *!TestingProfile::FinishInit
712 *!TestingProfile::TestingProfile
713 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
714 *!testing::Test::Run
715 }"""
717 suppress_96010 = [
718 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n",
719 ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" +
720 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"),
721 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n",
722 "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n",
723 # No name should be needed
724 "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n",
725 # Whole trace
726 ("UNADDRESSABLE ACCESS\n" +
727 "*!TestingProfile::FinishInit\n" +
728 "*!TestingProfile::TestingProfile\n" +
729 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
730 "*!testing::Test::Run\n"),
733 negative_96010 = [
734 # Wrong type
735 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
736 # No ellipsis
737 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
740 TestStack(stack_96010, suppress_96010, negative_96010,
741 suppression_parser=ReadDrMemorySuppressions)
743 # Invalid heap arg
744 stack_invalid = """{
745 INVALID HEAP ARGUMENT
746 name=asdf
747 *!foo
748 }"""
749 suppress_invalid = [
750 "INVALID HEAP ARGUMENT\n*!foo\n",
752 negative_invalid = [
753 "UNADDRESSABLE ACCESS\n*!foo\n",
756 TestStack(stack_invalid, suppress_invalid, negative_invalid,
757 suppression_parser=ReadDrMemorySuppressions)
759 # Suppress only ntdll
760 stack_in_ntdll = """{
761 UNADDRESSABLE ACCESS
762 name=<insert_a_suppression_name_here>
763 ntdll.dll!RtlTryEnterCriticalSection
764 }"""
765 stack_not_ntdll = """{
766 UNADDRESSABLE ACCESS
767 name=<insert_a_suppression_name_here>
768 notntdll.dll!RtlTryEnterCriticalSection
769 }"""
771 suppress_in_ntdll = [
772 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
774 suppress_in_any = [
775 "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
778 TestStack(stack_in_ntdll, suppress_in_ntdll + suppress_in_any, [],
779 suppression_parser=ReadDrMemorySuppressions)
780 # Make sure we don't wildcard away the "not" part and match ntdll.dll by
781 # accident.
782 TestStack(stack_not_ntdll, suppress_in_any, suppress_in_ntdll,
783 suppression_parser=ReadDrMemorySuppressions)
785 # Suppress a POSSIBLE LEAK with LEAK.
786 stack_foo_possible = """{
787 POSSIBLE LEAK
788 name=foo possible
789 *!foo
790 }"""
791 suppress_foo_possible = [ "POSSIBLE LEAK\n*!foo\n" ]
792 suppress_foo_leak = [ "LEAK\n*!foo\n" ]
793 TestStack(stack_foo_possible, suppress_foo_possible + suppress_foo_leak, [],
794 suppression_parser=ReadDrMemorySuppressions)
796 # Don't suppress LEAK with POSSIBLE LEAK.
797 stack_foo_leak = """{
798 LEAK
799 name=foo leak
800 *!foo
801 }"""
802 TestStack(stack_foo_leak, suppress_foo_leak, suppress_foo_possible,
803 suppression_parser=ReadDrMemorySuppressions)
805 # Test case insensitivity of module names.
806 stack_user32_mixed_case = """{
807 LEAK
808 name=<insert>
809 USER32.dll!foo
810 user32.DLL!bar
811 user32.dll!baz
812 }"""
813 suppress_user32 = [ # Module name case doesn't matter.
814 "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n",
815 "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n",
817 no_suppress_user32 = [ # Function name case matters.
818 "LEAK\nuser32.dll!FOO\nuser32.dll!BAR\nuser32.dll!BAZ\n",
819 "LEAK\nUSER32.DLL!FOO\nUSER32.DLL!BAR\nUSER32.DLL!BAZ\n",
821 TestStack(stack_user32_mixed_case, suppress_user32, no_suppress_user32,
822 suppression_parser=ReadDrMemorySuppressions)
824 # Test mod!... frames.
825 stack_kernel32_through_ntdll = """{
826 LEAK
827 name=<insert>
828 kernel32.dll!foo
829 KERNEL32.dll!bar
830 kernel32.DLL!baz
831 ntdll.dll!quux
832 }"""
833 suppress_mod_ellipsis = [
834 "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
835 "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
837 no_suppress_mod_ellipsis = [
838 # Need one or more matching frames, not zero, unlike regular ellipsis.
839 "LEAK\nuser32.dll!...\nkernel32.dll!...\nntdll.dll!quux\n",
841 TestStack(stack_kernel32_through_ntdll, suppress_mod_ellipsis,
842 no_suppress_mod_ellipsis,
843 suppression_parser=ReadDrMemorySuppressions)
845 # Test that the presubmit checks work.
846 forgot_to_name = """
847 UNADDRESSABLE ACCESS
848 name=<insert_a_suppression_name_here>
849 ntdll.dll!RtlTryEnterCriticalSection
851 TestFailPresubmit(forgot_to_name, 'forgotten to put a suppression',
852 suppression_parser=ReadDrMemorySuppressions)
854 named_twice = """
855 UNADDRESSABLE ACCESS
856 name=http://crbug.com/1234
857 *!foo
859 UNADDRESSABLE ACCESS
860 name=http://crbug.com/1234
861 *!bar
863 TestFailPresubmit(named_twice, 'defined more than once',
864 suppression_parser=ReadDrMemorySuppressions)
866 forgot_stack = """
867 UNADDRESSABLE ACCESS
868 name=http://crbug.com/1234
870 TestFailPresubmit(forgot_stack, 'has no stack frames',
871 suppression_parser=ReadDrMemorySuppressions)
873 ends_in_ellipsis = """
874 UNADDRESSABLE ACCESS
875 name=http://crbug.com/1234
876 ntdll.dll!RtlTryEnterCriticalSection
879 TestFailPresubmit(ends_in_ellipsis, 'ends in an ellipsis',
880 suppression_parser=ReadDrMemorySuppressions)
882 bad_stack_frame = """
883 UNADDRESSABLE ACCESS
884 name=http://crbug.com/1234
885 fun:memcheck_style_frame
887 TestFailPresubmit(bad_stack_frame, 'Unexpected stack frame pattern',
888 suppression_parser=ReadDrMemorySuppressions)
890 # Test FilenameToTool.
891 filenames_to_tools = {
892 "tools/valgrind/drmemory/suppressions.txt": "drmemory",
893 "tools/valgrind/drmemory/suppressions_full.txt": "drmemory",
894 "tools/valgrind/memcheck/suppressions.txt": "memcheck",
895 "tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
896 "asdf/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
897 "foo/bar/baz/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
898 "foo/bar/baz/tools/valgrind/suppressions.txt": None,
899 "tools/valgrind/suppressions.txt": None,
901 for (filename, expected_tool) in filenames_to_tools.items():
902 filename.replace('/', os.sep) # Make the path look native.
903 tool = FilenameToTool(filename)
904 assert tool == expected_tool, (
905 "failed to get expected tool for filename %r, expected %s, got %s" %
906 (filename, expected_tool, tool))
908 # Test ValgrindStyleSuppression.__str__.
909 supp = ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
910 ["...", "fun:foo"], "supp.txt:1")
911 # Intentional 3-space indent. =/
912 supp_str = ("{\n"
913 " http://crbug.com/1234\n"
914 " Memcheck:Leak\n"
915 " ...\n"
916 " fun:foo\n"
917 "}\n")
918 assert str(supp) == supp_str, (
919 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
921 # Test DrMemorySuppression.__str__.
922 supp = DrMemorySuppression(
923 "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
924 supp_str = ("LEAK\n"
925 "name=http://crbug.com/1234\n"
926 "...\n"
927 "*!foo\n")
928 assert str(supp) == supp_str, (
929 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
931 supp = DrMemorySuppression(
932 "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01",
933 ["ntdll.dll!*", "*!foo"], "supp.txt:1")
934 supp_str = ("UNINITIALIZED READ\n"
935 "name=http://crbug.com/1234\n"
936 "instruction=test 0x08(%eax) $0x01\n"
937 "ntdll.dll!*\n"
938 "*!foo\n")
939 assert str(supp) == supp_str, (
940 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
943 if __name__ == '__main__':
944 SelfTest()
945 print 'PASS'