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.
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>
18 fun:wildcarded_fun*_name
19 # an ellipsis wildcards zero or more functions in a stack.
21 fun:some_other_function_name
24 If ran from the command line, suppressions.py does a self-test
25 of the Suppression class.
32 sys
.path
.insert(0, os
.path
.join(os
.path
.dirname(__file__
),
33 '..', 'python', 'google'))
40 def GetSuppressions():
41 suppressions_root
= path_utils
.ScriptDir()
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
)
79 def GlobToRegex(glob_pattern
, ignore_case
=False):
80 """Translate glob wildcards (*?) into regex syntax. Escape the rest."""
82 for char
in glob_pattern
:
87 elif ignore_case
and char
.isalpha():
88 regex
+= '[%s%s]' % (char
.lower(), char
.upper())
90 regex
+= re
.escape(char
)
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('#'):
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.
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
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
139 suppression_from_report: list of strings (function names).
141 True if the suppression is not empty and matches the report.
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.
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
)
163 if tool
in ('heapcheck', 'drmemory', 'memcheck', 'tsan'):
168 def ReadSuppressionsFromFile(filename
):
169 """Read suppressions from the given file and return them as a list"""
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
):
185 input_file
= file(filename
, 'r')
187 return parse_func(input_file
, filename
)
188 except SuppressionError
:
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.
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
210 regex
+= GlobToRegex(line
)
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
)
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
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.
267 lines: a list of lines containing suppressions.
268 supp_descriptor: should typically be a filename.
269 Used only when printing errors.
275 in_suppression
= False
280 if line
.startswith('#'):
282 if not in_suppression
:
284 # empty lines between suppressions
286 elif line
.startswith('{'):
287 in_suppression
= True
290 raise SuppressionError('Expected: "{"',
291 "%s:%d" % (supp_descriptor
, nline
))
292 elif line
.startswith('}'):
294 ValgrindStyleSuppression(cur_descr
, cur_type
, cur_stack
,
295 "%s:%d" % (supp_descriptor
, nline
)))
299 in_suppression
= False
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
))
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())
326 raise SuppressionError(
327 '"fun:function_name" or "obj:object_file" or "..." expected',
328 "%s:%d" % (supp_descriptor
, nline
))
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
338 known_supp_names
= {} # Key: name, Value: suppression.
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
348 "You've forgotten to put a suppression name like bug_XXX",
352 if s
.description
in known_supp_names
:
355 'Suppression named "%s" is defined more than once, '
356 'see %s' % (s
.description
,
357 known_supp_names
[s
.description
].defined_at
),
360 known_supp_names
[s
.description
] = s
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())]
374 # TODO(timurrrr): warn on putting suppressions into a wrong file,
375 # e.g. TSan suppression in a memcheck file.
379 supps
= ReadSuppressionsFromFile(f
)
380 errors
.extend(PresubmitCheckSuppressions(supps
))
381 except SuppressionError
as e
:
384 return [output_api
.PresubmitError(str(e
)) for e
in errors
]
387 class DrMemorySuppression(Suppression
):
388 """A suppression using the DrMemory syntax.
391 instr: The instruction to match.
392 Rest inherited from Suppression.
395 def __init__(self
, name
, report_type
, instr
, stack
, defined_at
):
399 # Construct the regex.
401 if report_type
== 'LEAK':
402 regex
+= '(POSSIBLE )?LEAK'
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
413 regex
+= 'instruction=%s\n' % GlobToRegex(instr
)
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))
427 regex
+= GlobToRegex(line
)
429 regex
+= '(.*\n)*' # Match anything left in the stack.
431 return super(DrMemorySuppression
, self
).__init
__(name
, report_type
, stack
,
436 text
= self
.type + "\n"
438 text
+= "name=%s\n" % self
.description
440 text
+= "instruction=%s\n" % self
.instr
441 text
+= "\n".join(self
.stack
)
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',
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.
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
)
482 for (line_no
, line
) in lines
:
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.
496 defined_at
= "%s:%d" % (supp_descriptor
, line_no
)
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=', '')
504 # Unrecognized prefix indicates start of stack trace.
507 # Blank line means end of suppression.
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' +
515 ' <module+0xhexoffset>\n' +
516 ' <not in a module>\n' +
517 ' system call Name\n' +
519 ' ...\n') % line_no
, defined_at
)
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
)
530 DrMemorySuppression(name
, report_type
, instr
, stack
, defined_at
))
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.
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.
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
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
)))
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])))
596 """Tests the Suppression.Match() capabilities."""
598 test_memcheck_stack_1
= """{
608 test_memcheck_stack_2
= """{
610 Memcheck:Uninitialized
618 test_memcheck_stack_3
= """{
620 Memcheck:Unaddressable
628 test_memcheck_stack_4
= """{
638 test_heapcheck_stack
= """{
648 test_tsan_stack
= """{
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.
781 name=<insert_a_suppression_name_here>
782 *!TestingProfile::FinishInit
783 *!TestingProfile::TestingProfile
784 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
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",
797 ("UNADDRESSABLE ACCESS\n" +
798 "*!TestingProfile::FinishInit\n" +
799 "*!TestingProfile::TestingProfile\n" +
800 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
801 "*!testing::Test::Run\n"),
806 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
808 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
811 TestStack(stack_96010
, suppress_96010
, negative_96010
,
812 suppression_parser
=ReadDrMemorySuppressions
)
816 INVALID HEAP ARGUMENT
821 "INVALID HEAP ARGUMENT\n*!foo\n",
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
= """{
833 name=<insert_a_suppression_name_here>
834 ntdll.dll!RtlTryEnterCriticalSection
836 stack_not_ntdll
= """{
838 name=<insert_a_suppression_name_here>
839 notntdll.dll!RtlTryEnterCriticalSection
842 suppress_in_ntdll
= [
843 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
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
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
= """{
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
= """{
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
= """{
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
= """{
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.
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
)
927 name=http://crbug.com/1234
931 name=http://crbug.com/1234
934 TestFailPresubmit(named_twice
, 'defined more than once',
935 suppression_parser
=ReadDrMemorySuppressions
)
939 name=http://crbug.com/1234
941 TestFailPresubmit(forgot_stack
, 'has no stack frames',
942 suppression_parser
=ReadDrMemorySuppressions
)
944 ends_in_ellipsis
= """
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
= """
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. =/
986 " http://crbug.com/1234\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")
998 "name=http://crbug.com/1234\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"
1012 assert str(supp
) == supp_str
, (
1013 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
1016 if __name__
== '__main__':