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.
35 def GlobToRegex(glob_pattern
, ignore_case
=False):
36 """Translate glob wildcards (*?) into regex syntax. Escape the rest."""
38 for char
in glob_pattern
:
43 elif ignore_case
and char
.isalpha():
44 regex
+= '[%s%s]' % (char
.lower(), char
.upper())
46 regex
+= re
.escape(char
)
50 def StripAndSkipCommentsIterator(lines
):
51 """Generator of (line_no, line) pairs that strips comments and whitespace."""
52 for (line_no
, line
) in enumerate(lines
):
53 line
= line
.strip() # Drop \n
54 if line
.startswith('#'):
56 # Skip comment lines, but not empty lines, they indicate the end of a
57 # suppression. Add one to the line number as well, since most editors use
58 # 1-based numberings, and enumerate is 0-based.
59 yield (line_no
+ 1, line
)
62 class Suppression(object):
63 """This class represents a single stack trace suppression.
66 description: A string representing the error description.
67 type: A string representing the error type, e.g. Memcheck:Leak.
68 stack: The lines comprising the stack trace for the suppression.
69 regex: The actual regex used to match against scraped reports.
72 def __init__(self
, description
, type, stack
, defined_at
, regex
):
75 description, type, stack, regex: same as class attributes
76 defined_at: file:line identifying where the suppression was defined
78 self
.description
= description
81 self
.defined_at
= defined_at
82 self
.regex
= re
.compile(regex
, re
.MULTILINE
)
84 def Match(self
, suppression_from_report
):
85 """Returns bool indicating whether this suppression matches
86 the suppression generated from Valgrind error report.
88 We match our suppressions against generated suppressions
89 (not against reports) since they have the same format
90 while the reports are taken from XML, contain filenames,
91 they are demangled, and are generally more difficult to
95 suppression_from_report: list of strings (function names).
97 True if the suppression is not empty and matches the report.
101 lines
= [f
.strip() for f
in suppression_from_report
]
102 return self
.regex
.match('\n'.join(lines
) + '\n') is not None
105 def FilenameToTool(filename
):
106 """Return the name of the tool that a file is related to, or None.
109 tools/heapcheck/suppressions.txt -> heapcheck
110 tools/valgrind/tsan/suppressions.txt -> tsan
111 tools/valgrind/drmemory/suppressions.txt -> drmemory
112 tools/valgrind/drmemory/suppressions_full.txt -> drmemory
113 tools/valgrind/memcheck/suppressions.txt -> memcheck
114 tools/valgrind/memcheck/suppressions_mac.txt -> memcheck
116 filename
= os
.path
.abspath(filename
)
117 parts
= filename
.split(os
.sep
)
119 if tool
in ('heapcheck', 'drmemory', 'memcheck', 'tsan'):
124 def ReadSuppressionsFromFile(filename
):
125 """Read suppressions from the given file and return them as a list"""
127 "drmemory": ReadDrMemorySuppressions
,
128 "memcheck": ReadValgrindStyleSuppressions
,
129 "tsan": ReadValgrindStyleSuppressions
,
130 "heapcheck": ReadValgrindStyleSuppressions
,
132 tool
= FilenameToTool(filename
)
133 assert tool
in tool_to_parser
, (
134 "unknown tool %s for filename %s" % (tool
, filename
))
135 parse_func
= tool_to_parser
[tool
]
137 input_file
= file(filename
, 'r')
139 return parse_func(input_file
, filename
)
140 except SuppressionError
:
145 class ValgrindStyleSuppression(Suppression
):
146 """A suppression using the Valgrind syntax.
148 Most tools, even ones that are not Valgrind-based, use this syntax, ie
149 Heapcheck, TSan, etc.
155 def __init__(self
, description
, type, stack
, defined_at
):
156 """Creates a suppression using the Memcheck, TSan, and Heapcheck syntax."""
157 regex
= '{\n.*\n%s\n' % type
162 regex
+= GlobToRegex(line
)
167 # In the recent version of valgrind-variant we've switched
168 # from memcheck's default Addr[1248]/Value[1248]/Cond suppression types
169 # to simply Unaddressable/Uninitialized.
170 # The suppression generator no longer gives us "old" types thus
171 # for the "new-type" suppressions:
172 # * Memcheck:Unaddressable should also match Addr* reports,
173 # * Memcheck:Uninitialized should also match Cond and Value reports,
175 # We also want to support legacy suppressions (e.g. copied from
176 # upstream bugs etc), so:
177 # * Memcheck:Addr[1248] suppressions should match Unaddressable reports,
178 # * Memcheck:Cond and Memcheck:Value[1248] should match Uninitialized.
179 # Please note the latest two rules only apply to the
180 # tools/valgrind/waterfall.sh suppression matcher and the real
181 # valgrind-variant Memcheck will not suppress
182 # e.g. Addr1 printed as Unaddressable with Addr4 suppression.
183 # Be careful to check the access size while copying legacy suppressions!
184 for sz
in [1, 2, 4, 8]:
185 regex
= regex
.replace("\nMemcheck:Addr%d\n" % sz
,
186 "\nMemcheck:(Addr%d|Unaddressable)\n" % sz
)
187 regex
= regex
.replace("\nMemcheck:Value%d\n" % sz
,
188 "\nMemcheck:(Value%d|Uninitialized)\n" % sz
)
189 regex
= regex
.replace("\nMemcheck:Cond\n",
190 "\nMemcheck:(Cond|Uninitialized)\n")
191 regex
= regex
.replace("\nMemcheck:Unaddressable\n",
192 "\nMemcheck:(Addr.|Unaddressable)\n")
193 regex
= regex
.replace("\nMemcheck:Uninitialized\n",
194 "\nMemcheck:(Cond|Value.|Uninitialized)\n")
196 return super(ValgrindStyleSuppression
, self
).__init
__(
197 description
, type, stack
, defined_at
, regex
)
201 lines
= [self
.description
, self
.type] + self
.stack
202 return "{\n %s\n}\n" % "\n ".join(lines
)
205 class SuppressionError(Exception):
206 def __init__(self
, message
, happened_at
):
207 self
._message
= message
208 self
._happened
_at
= happened_at
211 return 'Error reading suppressions at %s!\n%s' % (
212 self
._happened
_at
, self
._message
)
215 def ReadValgrindStyleSuppressions(lines
, supp_descriptor
):
216 """Given a list of lines, returns a list of suppressions.
219 lines: a list of lines containing suppressions.
220 supp_descriptor: should typically be a filename.
221 Used only when printing errors.
227 in_suppression
= False
232 if line
.startswith('#'):
234 if not in_suppression
:
236 # empty lines between suppressions
238 elif line
.startswith('{'):
239 in_suppression
= True
242 raise SuppressionError('Expected: "{"',
243 "%s:%d" % (supp_descriptor
, nline
))
244 elif line
.startswith('}'):
246 ValgrindStyleSuppression(cur_descr
, cur_type
, cur_stack
,
247 "%s:%d" % (supp_descriptor
, nline
)))
251 in_suppression
= False
256 if (not line
.startswith("Memcheck:") and
257 not line
.startswith("ThreadSanitizer:") and
258 (line
!= "Heapcheck:Leak")):
259 raise SuppressionError(
260 'Expected "Memcheck:TYPE", "ThreadSanitizer:TYPE" '
261 'or "Heapcheck:Leak", got "%s"' % line
,
262 "%s:%d" % (supp_descriptor
, nline
))
263 supp_type
= line
.split(':')[1]
264 if not supp_type
in ["Addr1", "Addr2", "Addr4", "Addr8",
265 "Cond", "Free", "Jump", "Leak", "Overlap", "Param",
266 "Value1", "Value2", "Value4", "Value8",
267 "Race", "UnlockNonLocked", "InvalidLock",
268 "Unaddressable", "Uninitialized"]:
269 raise SuppressionError('Unknown suppression type "%s"' % supp_type
,
270 "%s:%d" % (supp_descriptor
, nline
))
273 elif re
.match("^fun:.*|^obj:.*|^\.\.\.$", line
):
274 cur_stack
.append(line
.strip())
275 elif len(cur_stack
) == 0 and cur_type
== "Memcheck:Param":
276 cur_stack
.append(line
.strip())
278 raise SuppressionError(
279 '"fun:function_name" or "obj:object_file" or "..." expected',
280 "%s:%d" % (supp_descriptor
, nline
))
284 def PresubmitCheckSuppressions(supps
):
285 """Check a list of suppressions and return a list of SuppressionErrors.
287 Mostly useful for separating the checking logic from the Presubmit API for
290 known_supp_names
= {} # Key: name, Value: suppression.
293 if re
.search("<.*suppression.name.here>", s
.description
):
294 # Suppression name line is
295 # <insert_a_suppression_name_here> for Memcheck,
296 # <Put your suppression name here> for TSan,
297 # name=<insert_a_suppression_name_here> for DrMemory
300 "You've forgotten to put a suppression name like bug_XXX",
304 if s
.description
in known_supp_names
:
307 'Suppression named "%s" is defined more than once, '
308 'see %s' % (s
.description
,
309 known_supp_names
[s
.description
].defined_at
),
312 known_supp_names
[s
.description
] = s
316 def PresubmitCheck(input_api
, output_api
):
317 """A helper function useful in PRESUBMIT.py
318 Returns a list of errors or [].
320 sup_regex
= re
.compile('suppressions.*\.txt$')
321 filenames
= [f
.AbsoluteLocalPath() for f
in input_api
.AffectedFiles()
322 if sup_regex
.search(f
.LocalPath())]
326 # TODO(timurrrr): warn on putting suppressions into a wrong file,
327 # e.g. TSan suppression in a memcheck file.
331 supps
= ReadSuppressionsFromFile(f
)
332 errors
.extend(PresubmitCheckSuppressions(supps
))
333 except SuppressionError
as e
:
336 return [output_api
.PresubmitError(str(e
)) for e
in errors
]
339 class DrMemorySuppression(Suppression
):
340 """A suppression using the DrMemory syntax.
343 instr: The instruction to match.
344 Rest inherited from Suppression.
347 def __init__(self
, name
, report_type
, instr
, stack
, defined_at
):
351 # Construct the regex.
353 if report_type
== 'LEAK':
354 regex
+= '(POSSIBLE )?LEAK'
357 regex
+= '\nname=.*\n'
359 # TODO(rnk): Implement http://crbug.com/107416#c5 .
360 # drmemory_analyze.py doesn't generate suppressions with an instruction in
361 # them, so these suppressions will always fail to match. We should override
362 # Match to fetch the instruction from the report and try to match against
365 regex
+= 'instruction=%s\n' % GlobToRegex(instr
)
371 (mod
, func
) = line
.split('!')
372 if func
== ELLIPSIS
: # mod!ellipsis frame
373 regex
+= '(%s\!.*\n)+' % GlobToRegex(mod
, ignore_case
=True)
374 else: # mod!func frame
375 # Ignore case for the module match, but not the function match.
376 regex
+= '%s\!%s\n' % (GlobToRegex(mod
, ignore_case
=True),
377 GlobToRegex(func
, ignore_case
=False))
379 regex
+= GlobToRegex(line
)
381 regex
+= '(.*\n)*' # Match anything left in the stack.
383 return super(DrMemorySuppression
, self
).__init
__(name
, report_type
, stack
,
388 text
= self
.type + "\n"
390 text
+= "name=%s\n" % self
.description
392 text
+= "instruction=%s\n" % self
.instr
393 text
+= "\n".join(self
.stack
)
398 # Possible DrMemory error report types. Keep consistent with suppress_name
399 # array in drmemory/drmemory/report.c.
400 DRMEMORY_ERROR_TYPES
= [
401 'UNADDRESSABLE ACCESS',
402 'UNINITIALIZED READ',
403 'INVALID HEAP ARGUMENT',
410 # Regexes to match valid drmemory frames.
411 DRMEMORY_FRAME_PATTERNS
= [
412 re
.compile(r
"^.*\!.*$"), # mod!func
413 re
.compile(r
"^.*!\.\.\.$"), # mod!ellipsis
414 re
.compile(r
"^\<.*\+0x.*\>$"), # <mod+0xoffs>
415 re
.compile(r
"^\<not in a module\>$"),
416 re
.compile(r
"^system call .*$"),
417 re
.compile(r
"^\*$"), # wildcard
418 re
.compile(r
"^\.\.\.$"), # ellipsis
422 def ReadDrMemorySuppressions(lines
, supp_descriptor
):
423 """Given a list of lines, returns a list of DrMemory suppressions.
426 lines: a list of lines containing suppressions.
427 supp_descriptor: should typically be a filename.
428 Used only when parsing errors happen.
430 lines
= StripAndSkipCommentsIterator(lines
)
432 for (line_no
, line
) in lines
:
435 if line
not in DRMEMORY_ERROR_TYPES
:
436 raise SuppressionError('Expected a DrMemory error type, '
437 'found %r instead\n Valid error types: %s' %
438 (line
, ' '.join(DRMEMORY_ERROR_TYPES
)),
439 "%s:%d" % (supp_descriptor
, line_no
))
441 # Suppression starts here.
446 defined_at
= "%s:%d" % (supp_descriptor
, line_no
)
448 for (line_no
, line
) in lines
:
449 if not found_stack
and line
.startswith('name='):
450 name
= line
.replace('name=', '')
451 elif not found_stack
and line
.startswith('instruction='):
452 instr
= line
.replace('instruction=', '')
454 # Unrecognized prefix indicates start of stack trace.
457 # Blank line means end of suppression.
459 if not any([regex
.match(line
) for regex
in DRMEMORY_FRAME_PATTERNS
]):
460 raise SuppressionError(
461 ('Unexpected stack frame pattern at line %d\n' +
462 'Frames should be one of the following:\n' +
463 ' module!function\n' +
465 ' <module+0xhexoffset>\n' +
466 ' <not in a module>\n' +
467 ' system call Name\n' +
469 ' ...\n') % line_no
, defined_at
)
472 if len(stack
) == 0: # In case we hit EOF or blank without any stack frames.
473 raise SuppressionError('Suppression "%s" has no stack frames, ends at %d'
474 % (name
, line_no
), defined_at
)
475 if stack
[-1] == ELLIPSIS
:
476 raise SuppressionError('Suppression "%s" ends in an ellipsis on line %d' %
477 (name
, line_no
), defined_at
)
480 DrMemorySuppression(name
, report_type
, instr
, stack
, defined_at
))
485 def ParseSuppressionOfType(lines
, supp_descriptor
, def_line_no
, report_type
):
486 """Parse the suppression starting on this line.
488 Suppressions start with a type, have an optional name and instruction, and a
489 stack trace that ends in a blank line.
494 def TestStack(stack
, positive
, negative
, suppression_parser
=None):
495 """A helper function for SelfTest() that checks a single stack.
498 stack: the stack to match the suppressions.
499 positive: the list of suppressions that must match the given stack.
500 negative: the list of suppressions that should not match.
501 suppression_parser: optional arg for the suppression parser, default is
502 ReadValgrindStyleSuppressions.
504 if not suppression_parser
:
505 suppression_parser
= ReadValgrindStyleSuppressions
506 for supp
in positive
:
507 parsed
= suppression_parser(supp
.split("\n"), "positive_suppression")
508 assert parsed
[0].Match(stack
.split("\n")), (
509 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp
, stack
))
510 for supp
in negative
:
511 parsed
= suppression_parser(supp
.split("\n"), "negative_suppression")
512 assert not parsed
[0].Match(stack
.split("\n")), (
513 "Suppression:\n%s\ndid match stack:\n%s" % (supp
, stack
))
516 def TestFailPresubmit(supp_text
, error_text
, suppression_parser
=None):
517 """A helper function for SelfTest() that verifies a presubmit check fires.
520 supp_text: suppression text to parse.
521 error_text: text of the presubmit error we expect to find.
522 suppression_parser: optional arg for the suppression parser, default is
523 ReadValgrindStyleSuppressions.
525 if not suppression_parser
:
526 suppression_parser
= ReadValgrindStyleSuppressions
528 supps
= suppression_parser(supp_text
.split("\n"), "<presubmit suppression>")
529 except SuppressionError
, e
:
530 # If parsing raised an exception, match the error text here.
531 assert error_text
in str(e
), (
532 "presubmit text %r not in SuppressionError:\n%r" %
533 (error_text
, str(e
)))
535 # Otherwise, run the presubmit checks over the supps. We expect a single
536 # error that has text matching error_text.
537 errors
= PresubmitCheckSuppressions(supps
)
538 assert len(errors
) == 1, (
539 "expected exactly one presubmit error, got:\n%s" % errors
)
540 assert error_text
in str(errors
[0]), (
541 "presubmit text %r not in SuppressionError:\n%r" %
542 (error_text
, str(errors
[0])))
546 """Tests the Suppression.Match() capabilities."""
548 test_memcheck_stack_1
= """{
558 test_memcheck_stack_2
= """{
560 Memcheck:Uninitialized
568 test_memcheck_stack_3
= """{
570 Memcheck:Unaddressable
578 test_memcheck_stack_4
= """{
588 test_heapcheck_stack
= """{
598 test_tsan_stack
= """{
609 positive_memcheck_suppressions_1
= [
610 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
611 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n}",
612 "{\nzzz\nMemcheck:Leak\nfun:absolutly\nfun:brilliant\n}",
613 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\n}",
614 "{\nzzz\nMemcheck:Leak\n...\nfun:detection\n}",
615 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n...\nfun:detection\n}",
616 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n...\nfun:detection\n}",
617 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\n}",
618 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\nfun:detection\n}",
619 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\nobj:condition\n}",
622 positive_memcheck_suppressions_2
= [
623 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
624 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*ly\n}",
625 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\nfun:brilliant\n}",
626 # Legacy suppression types
627 "{\nzzz\nMemcheck:Value1\n...\nfun:brilliant\n}",
628 "{\nzzz\nMemcheck:Cond\n...\nfun:detection\n}",
629 "{\nzzz\nMemcheck:Value8\nfun:absolutly\nfun:brilliant\n}",
632 positive_memcheck_suppressions_3
= [
633 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
634 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
635 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
636 # Legacy suppression types
637 "{\nzzz\nMemcheck:Addr1\n...\nfun:brilliant\n}",
638 "{\nzzz\nMemcheck:Addr8\n...\nfun:detection\n}",
641 positive_memcheck_suppressions_4
= [
642 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\n}",
643 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
644 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\nfun:brilliant\n}",
645 "{\nzzz\nMemcheck:Unaddressable\n...\nfun:brilliant\n}",
646 "{\nzzz\nMemcheck:Addr4\n...\nfun:detection\n}",
649 positive_heapcheck_suppressions
= [
650 "{\nzzz\nHeapcheck:Leak\n...\nobj:condition\n}",
651 "{\nzzz\nHeapcheck:Leak\nfun:absolutly\n}",
654 positive_tsan_suppressions
= [
655 "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}",
656 "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}",
659 negative_memcheck_suppressions_1
= [
660 "{\nzzz\nMemcheck:Leak\nfun:abnormal\n}",
661 "{\nzzz\nMemcheck:Leak\nfun:ab*liant\n}",
662 "{\nzzz\nMemcheck:Leak\nfun:brilliant\n}",
663 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
664 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
667 negative_memcheck_suppressions_2
= [
668 "{\nzzz\nMemcheck:Cond\nfun:abnormal\n}",
669 "{\nzzz\nMemcheck:Value2\nfun:abnormal\n}",
670 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*liant\n}",
671 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
672 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
673 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
674 "{\nzzz\nMemcheck:Unaddressable\nfun:brilliant\n}",
677 negative_memcheck_suppressions_3
= [
678 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
679 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
680 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
681 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
682 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
683 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
686 negative_memcheck_suppressions_4
= [
687 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
688 "{\nzzz\nMemcheck:Addr4\nfun:abnormal\n}",
689 "{\nzzz\nMemcheck:Unaddressable\nfun:abnormal\n}",
690 "{\nzzz\nMemcheck:Addr1\nfun:absolutly\n}",
691 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
692 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
693 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
694 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
697 negative_heapcheck_suppressions
= [
698 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
699 "{\nzzz\nHeapcheck:Leak\nfun:brilliant\n}",
702 negative_tsan_suppressions
= [
703 "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}",
704 "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}",
707 TestStack(test_memcheck_stack_1
,
708 positive_memcheck_suppressions_1
,
709 negative_memcheck_suppressions_1
)
710 TestStack(test_memcheck_stack_2
,
711 positive_memcheck_suppressions_2
,
712 negative_memcheck_suppressions_2
)
713 TestStack(test_memcheck_stack_3
,
714 positive_memcheck_suppressions_3
,
715 negative_memcheck_suppressions_3
)
716 TestStack(test_memcheck_stack_4
,
717 positive_memcheck_suppressions_4
,
718 negative_memcheck_suppressions_4
)
719 TestStack(test_heapcheck_stack
, positive_heapcheck_suppressions
,
720 negative_heapcheck_suppressions
)
721 TestStack(test_tsan_stack
, positive_tsan_suppressions
,
722 negative_tsan_suppressions
)
724 # TODO(timurrrr): add TestFailPresubmit tests.
726 ### DrMemory self tests.
728 # http://crbug.com/96010 suppression.
731 name=<insert_a_suppression_name_here>
732 *!TestingProfile::FinishInit
733 *!TestingProfile::TestingProfile
734 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
739 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n",
740 ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" +
741 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"),
742 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n",
743 "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n",
744 # No name should be needed
745 "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n",
747 ("UNADDRESSABLE ACCESS\n" +
748 "*!TestingProfile::FinishInit\n" +
749 "*!TestingProfile::TestingProfile\n" +
750 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
751 "*!testing::Test::Run\n"),
756 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
758 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
761 TestStack(stack_96010
, suppress_96010
, negative_96010
,
762 suppression_parser
=ReadDrMemorySuppressions
)
766 INVALID HEAP ARGUMENT
771 "INVALID HEAP ARGUMENT\n*!foo\n",
774 "UNADDRESSABLE ACCESS\n*!foo\n",
777 TestStack(stack_invalid
, suppress_invalid
, negative_invalid
,
778 suppression_parser
=ReadDrMemorySuppressions
)
780 # Suppress only ntdll
781 stack_in_ntdll
= """{
783 name=<insert_a_suppression_name_here>
784 ntdll.dll!RtlTryEnterCriticalSection
786 stack_not_ntdll
= """{
788 name=<insert_a_suppression_name_here>
789 notntdll.dll!RtlTryEnterCriticalSection
792 suppress_in_ntdll
= [
793 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
796 "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
799 TestStack(stack_in_ntdll
, suppress_in_ntdll
+ suppress_in_any
, [],
800 suppression_parser
=ReadDrMemorySuppressions
)
801 # Make sure we don't wildcard away the "not" part and match ntdll.dll by
803 TestStack(stack_not_ntdll
, suppress_in_any
, suppress_in_ntdll
,
804 suppression_parser
=ReadDrMemorySuppressions
)
806 # Suppress a POSSIBLE LEAK with LEAK.
807 stack_foo_possible
= """{
812 suppress_foo_possible
= [ "POSSIBLE LEAK\n*!foo\n" ]
813 suppress_foo_leak
= [ "LEAK\n*!foo\n" ]
814 TestStack(stack_foo_possible
, suppress_foo_possible
+ suppress_foo_leak
, [],
815 suppression_parser
=ReadDrMemorySuppressions
)
817 # Don't suppress LEAK with POSSIBLE LEAK.
818 stack_foo_leak
= """{
823 TestStack(stack_foo_leak
, suppress_foo_leak
, suppress_foo_possible
,
824 suppression_parser
=ReadDrMemorySuppressions
)
826 # Test case insensitivity of module names.
827 stack_user32_mixed_case
= """{
834 suppress_user32
= [ # Module name case doesn't matter.
835 "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n",
836 "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n",
838 no_suppress_user32
= [ # Function name case matters.
839 "LEAK\nuser32.dll!FOO\nuser32.dll!BAR\nuser32.dll!BAZ\n",
840 "LEAK\nUSER32.DLL!FOO\nUSER32.DLL!BAR\nUSER32.DLL!BAZ\n",
842 TestStack(stack_user32_mixed_case
, suppress_user32
, no_suppress_user32
,
843 suppression_parser
=ReadDrMemorySuppressions
)
845 # Test mod!... frames.
846 stack_kernel32_through_ntdll
= """{
854 suppress_mod_ellipsis
= [
855 "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
856 "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
858 no_suppress_mod_ellipsis
= [
859 # Need one or more matching frames, not zero, unlike regular ellipsis.
860 "LEAK\nuser32.dll!...\nkernel32.dll!...\nntdll.dll!quux\n",
862 TestStack(stack_kernel32_through_ntdll
, suppress_mod_ellipsis
,
863 no_suppress_mod_ellipsis
,
864 suppression_parser
=ReadDrMemorySuppressions
)
866 # Test that the presubmit checks work.
869 name=<insert_a_suppression_name_here>
870 ntdll.dll!RtlTryEnterCriticalSection
872 TestFailPresubmit(forgot_to_name
, 'forgotten to put a suppression',
873 suppression_parser
=ReadDrMemorySuppressions
)
877 name=http://crbug.com/1234
881 name=http://crbug.com/1234
884 TestFailPresubmit(named_twice
, 'defined more than once',
885 suppression_parser
=ReadDrMemorySuppressions
)
889 name=http://crbug.com/1234
891 TestFailPresubmit(forgot_stack
, 'has no stack frames',
892 suppression_parser
=ReadDrMemorySuppressions
)
894 ends_in_ellipsis
= """
896 name=http://crbug.com/1234
897 ntdll.dll!RtlTryEnterCriticalSection
900 TestFailPresubmit(ends_in_ellipsis
, 'ends in an ellipsis',
901 suppression_parser
=ReadDrMemorySuppressions
)
903 bad_stack_frame
= """
905 name=http://crbug.com/1234
906 fun:memcheck_style_frame
908 TestFailPresubmit(bad_stack_frame
, 'Unexpected stack frame pattern',
909 suppression_parser
=ReadDrMemorySuppressions
)
911 # Test FilenameToTool.
912 filenames_to_tools
= {
913 "tools/heapcheck/suppressions.txt": "heapcheck",
914 "tools/valgrind/tsan/suppressions.txt": "tsan",
915 "tools/valgrind/drmemory/suppressions.txt": "drmemory",
916 "tools/valgrind/drmemory/suppressions_full.txt": "drmemory",
917 "tools/valgrind/memcheck/suppressions.txt": "memcheck",
918 "tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
919 "asdf/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
920 "foo/bar/baz/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
921 "foo/bar/baz/tools/valgrind/suppressions.txt": None,
922 "tools/valgrind/suppressions.txt": None,
924 for (filename
, expected_tool
) in filenames_to_tools
.items():
925 filename
.replace('/', os
.sep
) # Make the path look native.
926 tool
= FilenameToTool(filename
)
927 assert tool
== expected_tool
, (
928 "failed to get expected tool for filename %r, expected %s, got %s" %
929 (filename
, expected_tool
, tool
))
931 # Test ValgrindStyleSuppression.__str__.
932 supp
= ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
933 ["...", "fun:foo"], "supp.txt:1")
934 # Intentional 3-space indent. =/
936 " http://crbug.com/1234\n"
941 assert str(supp
) == supp_str
, (
942 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
944 # Test DrMemorySuppression.__str__.
945 supp
= DrMemorySuppression(
946 "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
948 "name=http://crbug.com/1234\n"
951 assert str(supp
) == supp_str
, (
952 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
954 supp
= DrMemorySuppression(
955 "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01",
956 ["ntdll.dll!*", "*!foo"], "supp.txt:1")
957 supp_str
= ("UNINITIALIZED READ\n"
958 "name=http://crbug.com/1234\n"
959 "instruction=test 0x08(%eax) $0x01\n"
962 assert str(supp
) == supp_str
, (
963 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
966 if __name__
== '__main__':