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
, "drmemory", "suppressions.txt")
69 result
['drmem_suppressions'] = ReadSuppressionsFromFile(supp_filename
)
70 supp_filename
= JOIN(suppressions_root
, "drmemory", "suppressions_full.txt")
71 result
['drmem_full_suppressions'] = ReadSuppressionsFromFile(supp_filename
)
76 def GlobToRegex(glob_pattern
, ignore_case
=False):
77 """Translate glob wildcards (*?) into regex syntax. Escape the rest."""
79 for char
in glob_pattern
:
84 elif ignore_case
and char
.isalpha():
85 regex
+= '[%s%s]' % (char
.lower(), char
.upper())
87 regex
+= re
.escape(char
)
91 def StripAndSkipCommentsIterator(lines
):
92 """Generator of (line_no, line) pairs that strips comments and whitespace."""
93 for (line_no
, line
) in enumerate(lines
):
94 line
= line
.strip() # Drop \n
95 if line
.startswith('#'):
97 # Skip comment lines, but not empty lines, they indicate the end of a
98 # suppression. Add one to the line number as well, since most editors use
99 # 1-based numberings, and enumerate is 0-based.
100 yield (line_no
+ 1, line
)
103 class Suppression(object):
104 """This class represents a single stack trace suppression.
107 description: A string representing the error description.
108 type: A string representing the error type, e.g. Memcheck:Leak.
109 stack: The lines comprising the stack trace for the suppression.
110 regex: The actual regex used to match against scraped reports.
113 def __init__(self
, description
, type, stack
, defined_at
, regex
):
114 """Inits Suppression.
116 description, type, stack, regex: same as class attributes
117 defined_at: file:line identifying where the suppression was defined
119 self
.description
= description
122 self
.defined_at
= defined_at
123 self
.regex
= re
.compile(regex
, re
.MULTILINE
)
125 def Match(self
, suppression_from_report
):
126 """Returns bool indicating whether this suppression matches
127 the suppression generated from Valgrind error report.
129 We match our suppressions against generated suppressions
130 (not against reports) since they have the same format
131 while the reports are taken from XML, contain filenames,
132 they are demangled, and are generally more difficult to
136 suppression_from_report: list of strings (function names).
138 True if the suppression is not empty and matches the report.
142 lines
= [f
.strip() for f
in suppression_from_report
]
143 return self
.regex
.match('\n'.join(lines
) + '\n') is not None
146 def FilenameToTool(filename
):
147 """Return the name of the tool that a file is related to, or None.
150 tools/valgrind/tsan/suppressions.txt -> tsan
151 tools/valgrind/drmemory/suppressions.txt -> drmemory
152 tools/valgrind/drmemory/suppressions_full.txt -> drmemory
153 tools/valgrind/memcheck/suppressions.txt -> memcheck
154 tools/valgrind/memcheck/suppressions_mac.txt -> memcheck
156 filename
= os
.path
.abspath(filename
)
157 parts
= filename
.split(os
.sep
)
159 if tool
in ('drmemory', 'memcheck', 'tsan'):
164 def ReadSuppressionsFromFile(filename
):
165 """Read suppressions from the given file and return them as a list"""
167 "drmemory": ReadDrMemorySuppressions
,
168 "memcheck": ReadValgrindStyleSuppressions
,
169 "tsan": ReadValgrindStyleSuppressions
,
171 tool
= FilenameToTool(filename
)
172 assert tool
in tool_to_parser
, (
173 "unknown tool %s for filename %s" % (tool
, filename
))
174 parse_func
= tool_to_parser
[tool
]
176 # Consider non-existent files to be empty.
177 if not os
.path
.exists(filename
):
180 input_file
= file(filename
, 'r')
182 return parse_func(input_file
, filename
)
183 except SuppressionError
:
188 class ValgrindStyleSuppression(Suppression
):
189 """A suppression using the Valgrind syntax.
191 Most tools, even ones that are not Valgrind-based, use this syntax, ie
198 def __init__(self
, description
, type, stack
, defined_at
):
199 """Creates a suppression using the Memcheck and TSan, syntax."""
200 regex
= '{\n.*\n%s\n' % type
205 regex
+= GlobToRegex(line
)
210 # In the recent version of valgrind-variant we've switched
211 # from memcheck's default Addr[1248]/Value[1248]/Cond suppression types
212 # to simply Unaddressable/Uninitialized.
213 # The suppression generator no longer gives us "old" types thus
214 # for the "new-type" suppressions:
215 # * Memcheck:Unaddressable should also match Addr* reports,
216 # * Memcheck:Uninitialized should also match Cond and Value reports,
218 # We also want to support legacy suppressions (e.g. copied from
219 # upstream bugs etc), so:
220 # * Memcheck:Addr[1248] suppressions should match Unaddressable reports,
221 # * Memcheck:Cond and Memcheck:Value[1248] should match Uninitialized.
222 # Please note the latest two rules only apply to the
223 # tools/valgrind/waterfall.sh suppression matcher and the real
224 # valgrind-variant Memcheck will not suppress
225 # e.g. Addr1 printed as Unaddressable with Addr4 suppression.
226 # Be careful to check the access size while copying legacy suppressions!
227 for sz
in [1, 2, 4, 8]:
228 regex
= regex
.replace("\nMemcheck:Addr%d\n" % sz
,
229 "\nMemcheck:(Addr%d|Unaddressable)\n" % sz
)
230 regex
= regex
.replace("\nMemcheck:Value%d\n" % sz
,
231 "\nMemcheck:(Value%d|Uninitialized)\n" % sz
)
232 regex
= regex
.replace("\nMemcheck:Cond\n",
233 "\nMemcheck:(Cond|Uninitialized)\n")
234 regex
= regex
.replace("\nMemcheck:Unaddressable\n",
235 "\nMemcheck:(Addr.|Unaddressable)\n")
236 regex
= regex
.replace("\nMemcheck:Uninitialized\n",
237 "\nMemcheck:(Cond|Value.|Uninitialized)\n")
239 return super(ValgrindStyleSuppression
, self
).__init
__(
240 description
, type, stack
, defined_at
, regex
)
244 lines
= [self
.description
, self
.type] + self
.stack
245 return "{\n %s\n}\n" % "\n ".join(lines
)
248 class SuppressionError(Exception):
249 def __init__(self
, message
, happened_at
):
250 self
._message
= message
251 self
._happened
_at
= happened_at
254 return 'Error reading suppressions at %s!\n%s' % (
255 self
._happened
_at
, self
._message
)
258 def ReadValgrindStyleSuppressions(lines
, supp_descriptor
):
259 """Given a list of lines, returns a list of suppressions.
262 lines: a list of lines containing suppressions.
263 supp_descriptor: should typically be a filename.
264 Used only when printing errors.
270 in_suppression
= False
275 if line
.startswith('#'):
277 if not in_suppression
:
279 # empty lines between suppressions
281 elif line
.startswith('{'):
282 in_suppression
= True
285 raise SuppressionError('Expected: "{"',
286 "%s:%d" % (supp_descriptor
, nline
))
287 elif line
.startswith('}'):
289 ValgrindStyleSuppression(cur_descr
, cur_type
, cur_stack
,
290 "%s:%d" % (supp_descriptor
, nline
)))
294 in_suppression
= False
299 if (not line
.startswith("Memcheck:") and
300 not line
.startswith("ThreadSanitizer:")):
301 raise SuppressionError(
302 'Expected "Memcheck:TYPE" or "ThreadSanitizer:TYPE", '
304 "%s:%d" % (supp_descriptor
, nline
))
305 supp_type
= line
.split(':')[1]
306 if not supp_type
in ["Addr1", "Addr2", "Addr4", "Addr8",
307 "Cond", "Free", "Jump", "Leak", "Overlap", "Param",
308 "Value1", "Value2", "Value4", "Value8",
309 "Race", "UnlockNonLocked", "InvalidLock",
310 "Unaddressable", "Uninitialized"]:
311 raise SuppressionError('Unknown suppression type "%s"' % supp_type
,
312 "%s:%d" % (supp_descriptor
, nline
))
315 elif re
.match("^fun:.*|^obj:.*|^\.\.\.$", line
):
316 cur_stack
.append(line
.strip())
317 elif len(cur_stack
) == 0 and cur_type
== "Memcheck:Param":
318 cur_stack
.append(line
.strip())
320 raise SuppressionError(
321 '"fun:function_name" or "obj:object_file" or "..." expected',
322 "%s:%d" % (supp_descriptor
, nline
))
326 def PresubmitCheckSuppressions(supps
):
327 """Check a list of suppressions and return a list of SuppressionErrors.
329 Mostly useful for separating the checking logic from the Presubmit API for
332 known_supp_names
= {} # Key: name, Value: suppression.
335 if re
.search("<.*suppression.name.here>", s
.description
):
336 # Suppression name line is
337 # <insert_a_suppression_name_here> for Memcheck,
338 # <Put your suppression name here> for TSan,
339 # name=<insert_a_suppression_name_here> for DrMemory
342 "You've forgotten to put a suppression name like bug_XXX",
346 if s
.description
in known_supp_names
:
349 'Suppression named "%s" is defined more than once, '
350 'see %s' % (s
.description
,
351 known_supp_names
[s
.description
].defined_at
),
354 known_supp_names
[s
.description
] = s
358 def PresubmitCheck(input_api
, output_api
):
359 """A helper function useful in PRESUBMIT.py
360 Returns a list of errors or [].
362 sup_regex
= re
.compile('suppressions.*\.txt$')
363 filenames
= [f
.AbsoluteLocalPath() for f
in input_api
.AffectedFiles()
364 if sup_regex
.search(f
.LocalPath())]
368 # TODO(timurrrr): warn on putting suppressions into a wrong file,
369 # e.g. TSan suppression in a memcheck file.
373 supps
= ReadSuppressionsFromFile(f
)
374 errors
.extend(PresubmitCheckSuppressions(supps
))
375 except SuppressionError
as e
:
378 return [output_api
.PresubmitError(str(e
)) for e
in errors
]
381 class DrMemorySuppression(Suppression
):
382 """A suppression using the DrMemory syntax.
385 instr: The instruction to match.
386 Rest inherited from Suppression.
389 def __init__(self
, name
, report_type
, instr
, stack
, defined_at
):
393 # Construct the regex.
395 if report_type
== 'LEAK':
396 regex
+= '(POSSIBLE )?LEAK'
399 regex
+= '\nname=.*\n'
401 # TODO(rnk): Implement http://crbug.com/107416#c5 .
402 # drmemory_analyze.py doesn't generate suppressions with an instruction in
403 # them, so these suppressions will always fail to match. We should override
404 # Match to fetch the instruction from the report and try to match against
407 regex
+= 'instruction=%s\n' % GlobToRegex(instr
)
413 (mod
, func
) = line
.split('!')
414 if func
== ELLIPSIS
: # mod!ellipsis frame
415 regex
+= '(%s\!.*\n)+' % GlobToRegex(mod
, ignore_case
=True)
416 else: # mod!func frame
417 # Ignore case for the module match, but not the function match.
418 regex
+= '%s\!%s\n' % (GlobToRegex(mod
, ignore_case
=True),
419 GlobToRegex(func
, ignore_case
=False))
421 regex
+= GlobToRegex(line
)
423 regex
+= '(.*\n)*' # Match anything left in the stack.
425 return super(DrMemorySuppression
, self
).__init
__(name
, report_type
, stack
,
430 text
= self
.type + "\n"
432 text
+= "name=%s\n" % self
.description
434 text
+= "instruction=%s\n" % self
.instr
435 text
+= "\n".join(self
.stack
)
440 # Possible DrMemory error report types. Keep consistent with suppress_name
441 # array in drmemory/drmemory/report.c.
442 DRMEMORY_ERROR_TYPES
= [
443 'UNADDRESSABLE ACCESS',
444 'UNINITIALIZED READ',
445 'INVALID HEAP ARGUMENT',
454 # Regexes to match valid drmemory frames.
455 DRMEMORY_FRAME_PATTERNS
= [
456 re
.compile(r
"^.*\!.*$"), # mod!func
457 re
.compile(r
"^.*!\.\.\.$"), # mod!ellipsis
458 re
.compile(r
"^\<.*\+0x.*\>$"), # <mod+0xoffs>
459 re
.compile(r
"^\<not in a module\>$"),
460 re
.compile(r
"^system call .*$"),
461 re
.compile(r
"^\*$"), # wildcard
462 re
.compile(r
"^\.\.\.$"), # ellipsis
466 def ReadDrMemorySuppressions(lines
, supp_descriptor
):
467 """Given a list of lines, returns a list of DrMemory suppressions.
470 lines: a list of lines containing suppressions.
471 supp_descriptor: should typically be a filename.
472 Used only when parsing errors happen.
474 lines
= StripAndSkipCommentsIterator(lines
)
476 for (line_no
, line
) in lines
:
479 if line
not in DRMEMORY_ERROR_TYPES
:
480 raise SuppressionError('Expected a DrMemory error type, '
481 'found %r instead\n Valid error types: %s' %
482 (line
, ' '.join(DRMEMORY_ERROR_TYPES
)),
483 "%s:%d" % (supp_descriptor
, line_no
))
485 # Suppression starts here.
490 defined_at
= "%s:%d" % (supp_descriptor
, line_no
)
492 for (line_no
, line
) in lines
:
493 if not found_stack
and line
.startswith('name='):
494 name
= line
.replace('name=', '')
495 elif not found_stack
and line
.startswith('instruction='):
496 instr
= line
.replace('instruction=', '')
498 # Unrecognized prefix indicates start of stack trace.
501 # Blank line means end of suppression.
503 if not any([regex
.match(line
) for regex
in DRMEMORY_FRAME_PATTERNS
]):
504 raise SuppressionError(
505 ('Unexpected stack frame pattern at line %d\n' +
506 'Frames should be one of the following:\n' +
507 ' module!function\n' +
509 ' <module+0xhexoffset>\n' +
510 ' <not in a module>\n' +
511 ' system call Name\n' +
513 ' ...\n') % line_no
, defined_at
)
516 if len(stack
) == 0: # In case we hit EOF or blank without any stack frames.
517 raise SuppressionError('Suppression "%s" has no stack frames, ends at %d'
518 % (name
, line_no
), defined_at
)
519 if stack
[-1] == ELLIPSIS
:
520 raise SuppressionError('Suppression "%s" ends in an ellipsis on line %d' %
521 (name
, line_no
), defined_at
)
524 DrMemorySuppression(name
, report_type
, instr
, stack
, defined_at
))
529 def ParseSuppressionOfType(lines
, supp_descriptor
, def_line_no
, report_type
):
530 """Parse the suppression starting on this line.
532 Suppressions start with a type, have an optional name and instruction, and a
533 stack trace that ends in a blank line.
538 def TestStack(stack
, positive
, negative
, suppression_parser
=None):
539 """A helper function for SelfTest() that checks a single stack.
542 stack: the stack to match the suppressions.
543 positive: the list of suppressions that must match the given stack.
544 negative: the list of suppressions that should not match.
545 suppression_parser: optional arg for the suppression parser, default is
546 ReadValgrindStyleSuppressions.
548 if not suppression_parser
:
549 suppression_parser
= ReadValgrindStyleSuppressions
550 for supp
in positive
:
551 parsed
= suppression_parser(supp
.split("\n"), "positive_suppression")
552 assert parsed
[0].Match(stack
.split("\n")), (
553 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp
, stack
))
554 for supp
in negative
:
555 parsed
= suppression_parser(supp
.split("\n"), "negative_suppression")
556 assert not parsed
[0].Match(stack
.split("\n")), (
557 "Suppression:\n%s\ndid match stack:\n%s" % (supp
, stack
))
560 def TestFailPresubmit(supp_text
, error_text
, suppression_parser
=None):
561 """A helper function for SelfTest() that verifies a presubmit check fires.
564 supp_text: suppression text to parse.
565 error_text: text of the presubmit error we expect to find.
566 suppression_parser: optional arg for the suppression parser, default is
567 ReadValgrindStyleSuppressions.
569 if not suppression_parser
:
570 suppression_parser
= ReadValgrindStyleSuppressions
572 supps
= suppression_parser(supp_text
.split("\n"), "<presubmit suppression>")
573 except SuppressionError
, e
:
574 # If parsing raised an exception, match the error text here.
575 assert error_text
in str(e
), (
576 "presubmit text %r not in SuppressionError:\n%r" %
577 (error_text
, str(e
)))
579 # Otherwise, run the presubmit checks over the supps. We expect a single
580 # error that has text matching error_text.
581 errors
= PresubmitCheckSuppressions(supps
)
582 assert len(errors
) == 1, (
583 "expected exactly one presubmit error, got:\n%s" % errors
)
584 assert error_text
in str(errors
[0]), (
585 "presubmit text %r not in SuppressionError:\n%r" %
586 (error_text
, str(errors
[0])))
590 """Tests the Suppression.Match() capabilities."""
592 test_memcheck_stack_1
= """{
602 test_memcheck_stack_2
= """{
604 Memcheck:Uninitialized
612 test_memcheck_stack_3
= """{
614 Memcheck:Unaddressable
622 test_memcheck_stack_4
= """{
632 test_tsan_stack
= """{
643 positive_memcheck_suppressions_1
= [
644 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
645 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n}",
646 "{\nzzz\nMemcheck:Leak\nfun:absolutly\nfun:brilliant\n}",
647 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\n}",
648 "{\nzzz\nMemcheck:Leak\n...\nfun:detection\n}",
649 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n...\nfun:detection\n}",
650 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n...\nfun:detection\n}",
651 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\n}",
652 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\nfun:detection\n}",
653 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\nobj:condition\n}",
656 positive_memcheck_suppressions_2
= [
657 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
658 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*ly\n}",
659 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\nfun:brilliant\n}",
660 # Legacy suppression types
661 "{\nzzz\nMemcheck:Value1\n...\nfun:brilliant\n}",
662 "{\nzzz\nMemcheck:Cond\n...\nfun:detection\n}",
663 "{\nzzz\nMemcheck:Value8\nfun:absolutly\nfun:brilliant\n}",
666 positive_memcheck_suppressions_3
= [
667 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
668 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
669 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}",
670 # Legacy suppression types
671 "{\nzzz\nMemcheck:Addr1\n...\nfun:brilliant\n}",
672 "{\nzzz\nMemcheck:Addr8\n...\nfun:detection\n}",
675 positive_memcheck_suppressions_4
= [
676 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\n}",
677 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}",
678 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\nfun:brilliant\n}",
679 "{\nzzz\nMemcheck:Unaddressable\n...\nfun:brilliant\n}",
680 "{\nzzz\nMemcheck:Addr4\n...\nfun:detection\n}",
683 positive_tsan_suppressions
= [
684 "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}",
685 "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}",
688 negative_memcheck_suppressions_1
= [
689 "{\nzzz\nMemcheck:Leak\nfun:abnormal\n}",
690 "{\nzzz\nMemcheck:Leak\nfun:ab*liant\n}",
691 "{\nzzz\nMemcheck:Leak\nfun:brilliant\n}",
692 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
693 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
696 negative_memcheck_suppressions_2
= [
697 "{\nzzz\nMemcheck:Cond\nfun:abnormal\n}",
698 "{\nzzz\nMemcheck:Value2\nfun:abnormal\n}",
699 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*liant\n}",
700 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
701 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
702 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
703 "{\nzzz\nMemcheck:Unaddressable\nfun:brilliant\n}",
706 negative_memcheck_suppressions_3
= [
707 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
708 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}",
709 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
710 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
711 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
712 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
715 negative_memcheck_suppressions_4
= [
716 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}",
717 "{\nzzz\nMemcheck:Addr4\nfun:abnormal\n}",
718 "{\nzzz\nMemcheck:Unaddressable\nfun:abnormal\n}",
719 "{\nzzz\nMemcheck:Addr1\nfun:absolutly\n}",
720 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}",
721 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}",
722 "{\nzzz\nMemcheck:Leak\nobj:condition\n}",
723 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}",
726 negative_tsan_suppressions
= [
727 "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}",
728 "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}",
731 TestStack(test_memcheck_stack_1
,
732 positive_memcheck_suppressions_1
,
733 negative_memcheck_suppressions_1
)
734 TestStack(test_memcheck_stack_2
,
735 positive_memcheck_suppressions_2
,
736 negative_memcheck_suppressions_2
)
737 TestStack(test_memcheck_stack_3
,
738 positive_memcheck_suppressions_3
,
739 negative_memcheck_suppressions_3
)
740 TestStack(test_memcheck_stack_4
,
741 positive_memcheck_suppressions_4
,
742 negative_memcheck_suppressions_4
)
743 TestStack(test_tsan_stack
, positive_tsan_suppressions
,
744 negative_tsan_suppressions
)
746 # TODO(timurrrr): add TestFailPresubmit tests.
748 ### DrMemory self tests.
750 # http://crbug.com/96010 suppression.
753 name=<insert_a_suppression_name_here>
754 *!TestingProfile::FinishInit
755 *!TestingProfile::TestingProfile
756 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
761 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n",
762 ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" +
763 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"),
764 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n",
765 "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n",
766 # No name should be needed
767 "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n",
769 ("UNADDRESSABLE ACCESS\n" +
770 "*!TestingProfile::FinishInit\n" +
771 "*!TestingProfile::TestingProfile\n" +
772 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
773 "*!testing::Test::Run\n"),
778 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
780 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
783 TestStack(stack_96010
, suppress_96010
, negative_96010
,
784 suppression_parser
=ReadDrMemorySuppressions
)
788 INVALID HEAP ARGUMENT
793 "INVALID HEAP ARGUMENT\n*!foo\n",
796 "UNADDRESSABLE ACCESS\n*!foo\n",
799 TestStack(stack_invalid
, suppress_invalid
, negative_invalid
,
800 suppression_parser
=ReadDrMemorySuppressions
)
802 # Suppress only ntdll
803 stack_in_ntdll
= """{
805 name=<insert_a_suppression_name_here>
806 ntdll.dll!RtlTryEnterCriticalSection
808 stack_not_ntdll
= """{
810 name=<insert_a_suppression_name_here>
811 notntdll.dll!RtlTryEnterCriticalSection
814 suppress_in_ntdll
= [
815 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
818 "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
821 TestStack(stack_in_ntdll
, suppress_in_ntdll
+ suppress_in_any
, [],
822 suppression_parser
=ReadDrMemorySuppressions
)
823 # Make sure we don't wildcard away the "not" part and match ntdll.dll by
825 TestStack(stack_not_ntdll
, suppress_in_any
, suppress_in_ntdll
,
826 suppression_parser
=ReadDrMemorySuppressions
)
828 # Suppress a POSSIBLE LEAK with LEAK.
829 stack_foo_possible
= """{
834 suppress_foo_possible
= [ "POSSIBLE LEAK\n*!foo\n" ]
835 suppress_foo_leak
= [ "LEAK\n*!foo\n" ]
836 TestStack(stack_foo_possible
, suppress_foo_possible
+ suppress_foo_leak
, [],
837 suppression_parser
=ReadDrMemorySuppressions
)
839 # Don't suppress LEAK with POSSIBLE LEAK.
840 stack_foo_leak
= """{
845 TestStack(stack_foo_leak
, suppress_foo_leak
, suppress_foo_possible
,
846 suppression_parser
=ReadDrMemorySuppressions
)
848 # Test case insensitivity of module names.
849 stack_user32_mixed_case
= """{
856 suppress_user32
= [ # Module name case doesn't matter.
857 "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n",
858 "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n",
860 no_suppress_user32
= [ # Function name case matters.
861 "LEAK\nuser32.dll!FOO\nuser32.dll!BAR\nuser32.dll!BAZ\n",
862 "LEAK\nUSER32.DLL!FOO\nUSER32.DLL!BAR\nUSER32.DLL!BAZ\n",
864 TestStack(stack_user32_mixed_case
, suppress_user32
, no_suppress_user32
,
865 suppression_parser
=ReadDrMemorySuppressions
)
867 # Test mod!... frames.
868 stack_kernel32_through_ntdll
= """{
876 suppress_mod_ellipsis
= [
877 "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
878 "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
880 no_suppress_mod_ellipsis
= [
881 # Need one or more matching frames, not zero, unlike regular ellipsis.
882 "LEAK\nuser32.dll!...\nkernel32.dll!...\nntdll.dll!quux\n",
884 TestStack(stack_kernel32_through_ntdll
, suppress_mod_ellipsis
,
885 no_suppress_mod_ellipsis
,
886 suppression_parser
=ReadDrMemorySuppressions
)
888 # Test that the presubmit checks work.
891 name=<insert_a_suppression_name_here>
892 ntdll.dll!RtlTryEnterCriticalSection
894 TestFailPresubmit(forgot_to_name
, 'forgotten to put a suppression',
895 suppression_parser
=ReadDrMemorySuppressions
)
899 name=http://crbug.com/1234
903 name=http://crbug.com/1234
906 TestFailPresubmit(named_twice
, 'defined more than once',
907 suppression_parser
=ReadDrMemorySuppressions
)
911 name=http://crbug.com/1234
913 TestFailPresubmit(forgot_stack
, 'has no stack frames',
914 suppression_parser
=ReadDrMemorySuppressions
)
916 ends_in_ellipsis
= """
918 name=http://crbug.com/1234
919 ntdll.dll!RtlTryEnterCriticalSection
922 TestFailPresubmit(ends_in_ellipsis
, 'ends in an ellipsis',
923 suppression_parser
=ReadDrMemorySuppressions
)
925 bad_stack_frame
= """
927 name=http://crbug.com/1234
928 fun:memcheck_style_frame
930 TestFailPresubmit(bad_stack_frame
, 'Unexpected stack frame pattern',
931 suppression_parser
=ReadDrMemorySuppressions
)
933 # Test FilenameToTool.
934 filenames_to_tools
= {
935 "tools/valgrind/tsan/suppressions.txt": "tsan",
936 "tools/valgrind/drmemory/suppressions.txt": "drmemory",
937 "tools/valgrind/drmemory/suppressions_full.txt": "drmemory",
938 "tools/valgrind/memcheck/suppressions.txt": "memcheck",
939 "tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
940 "asdf/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
941 "foo/bar/baz/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck",
942 "foo/bar/baz/tools/valgrind/suppressions.txt": None,
943 "tools/valgrind/suppressions.txt": None,
945 for (filename
, expected_tool
) in filenames_to_tools
.items():
946 filename
.replace('/', os
.sep
) # Make the path look native.
947 tool
= FilenameToTool(filename
)
948 assert tool
== expected_tool
, (
949 "failed to get expected tool for filename %r, expected %s, got %s" %
950 (filename
, expected_tool
, tool
))
952 # Test ValgrindStyleSuppression.__str__.
953 supp
= ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
954 ["...", "fun:foo"], "supp.txt:1")
955 # Intentional 3-space indent. =/
957 " http://crbug.com/1234\n"
962 assert str(supp
) == supp_str
, (
963 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
965 # Test DrMemorySuppression.__str__.
966 supp
= DrMemorySuppression(
967 "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
969 "name=http://crbug.com/1234\n"
972 assert str(supp
) == supp_str
, (
973 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
975 supp
= DrMemorySuppression(
976 "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01",
977 ["ntdll.dll!*", "*!foo"], "supp.txt:1")
978 supp_str
= ("UNINITIALIZED READ\n"
979 "name=http://crbug.com/1234\n"
980 "instruction=test 0x08(%eax) $0x01\n"
983 assert str(supp
) == supp_str
, (
984 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp
), supp_str
))
987 if __name__
== '__main__':