- add sources.
[platform/framework/web/crosswalk.git] / src / tools / valgrind / suppressions.py
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.
5
6 # suppressions.py
7
8 """Post-process Valgrind suppression matcher.
9
10 Suppressions are defined as follows:
11
12 # optional one-line comments anywhere in the suppressions file.
13 {
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
22 }
23
24 If ran from the command line, suppressions.py does a self-test
25 of the Suppression class.
26 """
27
28 import os
29 import re
30 import sys
31
32 sys.path.insert(0, os.path.join(os.path.dirname(__file__),
33                                 '..', 'python', 'google'))
34 import path_utils
35
36
37 ELLIPSIS = '...'
38
39
40 def GetSuppressions():
41   suppressions_root = path_utils.ScriptDir()
42   JOIN = os.path.join
43
44   result = {}
45
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
51
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
57
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
63
64   supp_filename = JOIN(suppressions_root, "tsan", "suppressions_win32.txt")
65   tsan_win = ReadSuppressionsFromFile(supp_filename)
66   result['win_suppressions'] = tsan_win
67
68   supp_filename = JOIN(suppressions_root, "..", "heapcheck", "suppressions.txt")
69   result['heapcheck_suppressions'] = ReadSuppressionsFromFile(supp_filename)
70
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)
75
76   return result
77
78
79 def GlobToRegex(glob_pattern, ignore_case=False):
80   """Translate glob wildcards (*?) into regex syntax.  Escape the rest."""
81   regex = ''
82   for char in glob_pattern:
83     if char == '*':
84       regex += '.*'
85     elif char == '?':
86       regex += '.'
87     elif ignore_case and char.isalpha():
88       regex += '[%s%s]' % (char.lower(), char.upper())
89     else:
90       regex += re.escape(char)
91   return ''.join(regex)
92
93
94 def StripAndSkipCommentsIterator(lines):
95   """Generator of (line_no, line) pairs that strips comments and whitespace."""
96   for (line_no, line) in enumerate(lines):
97     line = line.strip()  # Drop \n
98     if line.startswith('#'):
99       continue  # Comments
100     # Skip comment lines, but not empty lines, they indicate the end of a
101     # suppression.  Add one to the line number as well, since most editors use
102     # 1-based numberings, and enumerate is 0-based.
103     yield (line_no + 1, line)
104
105
106 class Suppression(object):
107   """This class represents a single stack trace suppression.
108
109   Attributes:
110     description: A string representing the error description.
111     type: A string representing the error type, e.g. Memcheck:Leak.
112     stack: The lines comprising the stack trace for the suppression.
113     regex: The actual regex used to match against scraped reports.
114   """
115
116   def __init__(self, description, type, stack, defined_at, regex):
117     """Inits Suppression.
118
119     description, type, stack, regex: same as class attributes
120     defined_at: file:line identifying where the suppression was defined
121     """
122     self.description = description
123     self.type = type
124     self.stack = stack
125     self.defined_at = defined_at
126     self.regex = re.compile(regex, re.MULTILINE)
127
128   def Match(self, suppression_from_report):
129     """Returns bool indicating whether this suppression matches
130        the suppression generated from Valgrind error report.
131
132        We match our suppressions against generated suppressions
133        (not against reports) since they have the same format
134        while the reports are taken from XML, contain filenames,
135        they are demangled, and are generally more difficult to
136        parse.
137
138     Args:
139       suppression_from_report: list of strings (function names).
140     Returns:
141       True if the suppression is not empty and matches the report.
142     """
143     if not self.stack:
144       return False
145     lines = [f.strip() for f in suppression_from_report]
146     return self.regex.match('\n'.join(lines) + '\n') is not None
147
148
149 def FilenameToTool(filename):
150   """Return the name of the tool that a file is related to, or None.
151
152   Example mappings:
153     tools/heapcheck/suppressions.txt -> heapcheck
154     tools/valgrind/tsan/suppressions.txt -> tsan
155     tools/valgrind/drmemory/suppressions.txt -> drmemory
156     tools/valgrind/drmemory/suppressions_full.txt -> drmemory
157     tools/valgrind/memcheck/suppressions.txt -> memcheck
158     tools/valgrind/memcheck/suppressions_mac.txt -> memcheck
159   """
160   filename = os.path.abspath(filename)
161   parts = filename.split(os.sep)
162   tool = parts[-2]
163   if tool in ('heapcheck', 'drmemory', 'memcheck', 'tsan'):
164     return tool
165   return None
166
167
168 def ReadSuppressionsFromFile(filename):
169   """Read suppressions from the given file and return them as a list"""
170   tool_to_parser = {
171     "drmemory":  ReadDrMemorySuppressions,
172     "memcheck":  ReadValgrindStyleSuppressions,
173     "tsan":      ReadValgrindStyleSuppressions,
174     "heapcheck": ReadValgrindStyleSuppressions,
175   }
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]
180
181   # Consider non-existent files to be empty.
182   if not os.path.exists(filename):
183     return []
184
185   input_file = file(filename, 'r')
186   try:
187     return parse_func(input_file, filename)
188   except SuppressionError:
189     input_file.close()
190     raise
191
192
193 class ValgrindStyleSuppression(Suppression):
194   """A suppression using the Valgrind syntax.
195
196   Most tools, even ones that are not Valgrind-based, use this syntax, ie
197   Heapcheck, TSan, etc.
198
199   Attributes:
200     Same as Suppression.
201   """
202
203   def __init__(self, description, type, stack, defined_at):
204     """Creates a suppression using the Memcheck, TSan, and Heapcheck syntax."""
205     regex = '{\n.*\n%s\n' % type
206     for line in stack:
207       if line == ELLIPSIS:
208         regex += '(.*\n)*'
209       else:
210         regex += GlobToRegex(line)
211         regex += '\n'
212     regex += '(.*\n)*'
213     regex += '}'
214
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,
222     #
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")
243
244     return super(ValgrindStyleSuppression, self).__init__(
245         description, type, stack, defined_at, regex)
246
247   def __str__(self):
248     """Stringify."""
249     lines = [self.description, self.type] + self.stack
250     return "{\n   %s\n}\n" % "\n   ".join(lines)
251
252
253 class SuppressionError(Exception):
254   def __init__(self, message, happened_at):
255     self._message = message
256     self._happened_at = happened_at
257
258   def __str__(self):
259     return 'Error reading suppressions at %s!\n%s' % (
260         self._happened_at, self._message)
261
262
263 def ReadValgrindStyleSuppressions(lines, supp_descriptor):
264   """Given a list of lines, returns a list of suppressions.
265
266   Args:
267     lines: a list of lines containing suppressions.
268     supp_descriptor: should typically be a filename.
269         Used only when printing errors.
270   """
271   result = []
272   cur_descr = ''
273   cur_type = ''
274   cur_stack = []
275   in_suppression = False
276   nline = 0
277   for line in lines:
278     nline += 1
279     line = line.strip()
280     if line.startswith('#'):
281       continue
282     if not in_suppression:
283       if not line:
284         # empty lines between suppressions
285         pass
286       elif line.startswith('{'):
287         in_suppression = True
288         pass
289       else:
290         raise SuppressionError('Expected: "{"',
291                                "%s:%d" % (supp_descriptor, nline))
292     elif line.startswith('}'):
293       result.append(
294           ValgrindStyleSuppression(cur_descr, cur_type, cur_stack,
295                                    "%s:%d" % (supp_descriptor, nline)))
296       cur_descr = ''
297       cur_type = ''
298       cur_stack = []
299       in_suppression = False
300     elif not cur_descr:
301       cur_descr = line
302       continue
303     elif not cur_type:
304       if (not line.startswith("Memcheck:") and
305           not line.startswith("ThreadSanitizer:") and
306           (line != "Heapcheck:Leak")):
307         raise SuppressionError(
308             'Expected "Memcheck:TYPE", "ThreadSanitizer:TYPE" '
309             'or "Heapcheck:Leak", got "%s"' % line,
310             "%s:%d" % (supp_descriptor, nline))
311       supp_type = line.split(':')[1]
312       if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8",
313                            "Cond", "Free", "Jump", "Leak", "Overlap", "Param",
314                            "Value1", "Value2", "Value4", "Value8",
315                            "Race", "UnlockNonLocked", "InvalidLock",
316                            "Unaddressable", "Uninitialized"]:
317         raise SuppressionError('Unknown suppression type "%s"' % supp_type,
318                                "%s:%d" % (supp_descriptor, nline))
319       cur_type = line
320       continue
321     elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line):
322       cur_stack.append(line.strip())
323     elif len(cur_stack) == 0 and cur_type == "Memcheck:Param":
324       cur_stack.append(line.strip())
325     else:
326       raise SuppressionError(
327           '"fun:function_name" or "obj:object_file" or "..." expected',
328           "%s:%d" % (supp_descriptor, nline))
329   return result
330
331
332 def PresubmitCheckSuppressions(supps):
333   """Check a list of suppressions and return a list of SuppressionErrors.
334
335   Mostly useful for separating the checking logic from the Presubmit API for
336   testing.
337   """
338   known_supp_names = {}  # Key: name, Value: suppression.
339   errors = []
340   for s in supps:
341     if re.search("<.*suppression.name.here>", s.description):
342       # Suppression name line is
343       # <insert_a_suppression_name_here> for Memcheck,
344       # <Put your suppression name here> for TSan,
345       # name=<insert_a_suppression_name_here> for DrMemory
346       errors.append(
347           SuppressionError(
348               "You've forgotten to put a suppression name like bug_XXX",
349               s.defined_at))
350       continue
351
352     if s.description in known_supp_names:
353       errors.append(
354           SuppressionError(
355               'Suppression named "%s" is defined more than once, '
356               'see %s' % (s.description,
357                           known_supp_names[s.description].defined_at),
358               s.defined_at))
359     else:
360       known_supp_names[s.description] = s
361   return errors
362
363
364 def PresubmitCheck(input_api, output_api):
365   """A helper function useful in PRESUBMIT.py
366      Returns a list of errors or [].
367   """
368   sup_regex = re.compile('suppressions.*\.txt$')
369   filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles()
370                    if sup_regex.search(f.LocalPath())]
371
372   errors = []
373
374   # TODO(timurrrr): warn on putting suppressions into a wrong file,
375   # e.g. TSan suppression in a memcheck file.
376
377   for f in filenames:
378     try:
379       supps = ReadSuppressionsFromFile(f)
380       errors.extend(PresubmitCheckSuppressions(supps))
381     except SuppressionError as e:
382       errors.append(e)
383
384   return [output_api.PresubmitError(str(e)) for e in errors]
385
386
387 class DrMemorySuppression(Suppression):
388   """A suppression using the DrMemory syntax.
389
390   Attributes:
391     instr: The instruction to match.
392     Rest inherited from Suppression.
393   """
394
395   def __init__(self, name, report_type, instr, stack, defined_at):
396     """Constructor."""
397     self.instr = instr
398
399     # Construct the regex.
400     regex = '{\n'
401     if report_type == 'LEAK':
402       regex += '(POSSIBLE )?LEAK'
403     else:
404       regex += report_type
405     regex += '\nname=.*\n'
406
407     # TODO(rnk): Implement http://crbug.com/107416#c5 .
408     # drmemory_analyze.py doesn't generate suppressions with an instruction in
409     # them, so these suppressions will always fail to match.  We should override
410     # Match to fetch the instruction from the report and try to match against
411     # that.
412     if instr:
413       regex += 'instruction=%s\n' % GlobToRegex(instr)
414
415     for line in stack:
416       if line == ELLIPSIS:
417         regex += '(.*\n)*'
418       elif '!' in line:
419         (mod, func) = line.split('!')
420         if func == ELLIPSIS:  # mod!ellipsis frame
421           regex += '(%s\!.*\n)+' % GlobToRegex(mod, ignore_case=True)
422         else:  # mod!func frame
423           # Ignore case for the module match, but not the function match.
424           regex += '%s\!%s\n' % (GlobToRegex(mod, ignore_case=True),
425                                  GlobToRegex(func, ignore_case=False))
426       else:
427         regex += GlobToRegex(line)
428         regex += '\n'
429     regex += '(.*\n)*'  # Match anything left in the stack.
430     regex += '}'
431     return super(DrMemorySuppression, self).__init__(name, report_type, stack,
432                                                      defined_at, regex)
433
434   def __str__(self):
435     """Stringify."""
436     text = self.type + "\n"
437     if self.description:
438       text += "name=%s\n" % self.description
439     if self.instr:
440       text += "instruction=%s\n" % self.instr
441     text += "\n".join(self.stack)
442     text += "\n"
443     return text
444
445
446 # Possible DrMemory error report types.  Keep consistent with suppress_name
447 # array in drmemory/drmemory/report.c.
448 DRMEMORY_ERROR_TYPES = [
449     'UNADDRESSABLE ACCESS',
450     'UNINITIALIZED READ',
451     'INVALID HEAP ARGUMENT',
452     'GDI USAGE ERROR',
453     'HANDLE LEAK',
454     'LEAK',
455     'POSSIBLE LEAK',
456     'WARNING',
457     ]
458
459
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
469     ]
470
471
472 def ReadDrMemorySuppressions(lines, supp_descriptor):
473   """Given a list of lines, returns a list of DrMemory suppressions.
474
475   Args:
476     lines: a list of lines containing suppressions.
477     supp_descriptor: should typically be a filename.
478       Used only when parsing errors happen.
479   """
480   lines = StripAndSkipCommentsIterator(lines)
481   suppressions = []
482   for (line_no, line) in lines:
483     if not line:
484       continue
485     if line not in DRMEMORY_ERROR_TYPES:
486       raise SuppressionError('Expected a DrMemory error type, '
487                              'found %r instead\n  Valid error types: %s' %
488                              (line, ' '.join(DRMEMORY_ERROR_TYPES)),
489                              "%s:%d" % (supp_descriptor, line_no))
490
491     # Suppression starts here.
492     report_type = line
493     name = ''
494     instr = None
495     stack = []
496     defined_at = "%s:%d" % (supp_descriptor, line_no)
497     found_stack = False
498     for (line_no, line) in lines:
499       if not found_stack and line.startswith('name='):
500         name = line.replace('name=', '')
501       elif not found_stack and line.startswith('instruction='):
502         instr = line.replace('instruction=', '')
503       else:
504         # Unrecognized prefix indicates start of stack trace.
505         found_stack = True
506         if not line:
507           # Blank line means end of suppression.
508           break
509         if not any([regex.match(line) for regex in DRMEMORY_FRAME_PATTERNS]):
510           raise SuppressionError(
511               ('Unexpected stack frame pattern at line %d\n' +
512                'Frames should be one of the following:\n' +
513                ' module!function\n' +
514                ' module!...\n' +
515                ' <module+0xhexoffset>\n' +
516                ' <not in a module>\n' +
517                ' system call Name\n' +
518                ' *\n' +
519                ' ...\n') % line_no, defined_at)
520         stack.append(line)
521
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)
528
529     suppressions.append(
530         DrMemorySuppression(name, report_type, instr, stack, defined_at))
531
532   return suppressions
533
534
535 def ParseSuppressionOfType(lines, supp_descriptor, def_line_no, report_type):
536   """Parse the suppression starting on this line.
537
538   Suppressions start with a type, have an optional name and instruction, and a
539   stack trace that ends in a blank line.
540   """
541
542
543
544 def TestStack(stack, positive, negative, suppression_parser=None):
545   """A helper function for SelfTest() that checks a single stack.
546
547   Args:
548     stack: the stack to match the suppressions.
549     positive: the list of suppressions that must match the given stack.
550     negative: the list of suppressions that should not match.
551     suppression_parser: optional arg for the suppression parser, default is
552       ReadValgrindStyleSuppressions.
553   """
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))
564
565
566 def TestFailPresubmit(supp_text, error_text, suppression_parser=None):
567   """A helper function for SelfTest() that verifies a presubmit check fires.
568
569   Args:
570     supp_text: suppression text to parse.
571     error_text: text of the presubmit error we expect to find.
572     suppression_parser: optional arg for the suppression parser, default is
573       ReadValgrindStyleSuppressions.
574   """
575   if not suppression_parser:
576     suppression_parser = ReadValgrindStyleSuppressions
577   try:
578     supps = suppression_parser(supp_text.split("\n"), "<presubmit suppression>")
579   except SuppressionError, e:
580     # If parsing raised an exception, match the error text here.
581     assert error_text in str(e), (
582         "presubmit text %r not in SuppressionError:\n%r" %
583         (error_text, str(e)))
584   else:
585     # Otherwise, run the presubmit checks over the supps.  We expect a single
586     # error that has text matching error_text.
587     errors = PresubmitCheckSuppressions(supps)
588     assert len(errors) == 1, (
589         "expected exactly one presubmit error, got:\n%s" % errors)
590     assert error_text in str(errors[0]), (
591         "presubmit text %r not in SuppressionError:\n%r" %
592         (error_text, str(errors[0])))
593
594
595 def SelfTest():
596   """Tests the Suppression.Match() capabilities."""
597
598   test_memcheck_stack_1 = """{
599     test
600     Memcheck:Leak
601     fun:absolutly
602     fun:brilliant
603     obj:condition
604     fun:detection
605     fun:expression
606   }"""
607
608   test_memcheck_stack_2 = """{
609     test
610     Memcheck:Uninitialized
611     fun:absolutly
612     fun:brilliant
613     obj:condition
614     fun:detection
615     fun:expression
616   }"""
617
618   test_memcheck_stack_3 = """{
619     test
620     Memcheck:Unaddressable
621     fun:absolutly
622     fun:brilliant
623     obj:condition
624     fun:detection
625     fun:expression
626   }"""
627
628   test_memcheck_stack_4 = """{
629     test
630     Memcheck:Addr4
631     fun:absolutly
632     fun:brilliant
633     obj:condition
634     fun:detection
635     fun:expression
636   }"""
637
638   test_heapcheck_stack = """{
639     test
640     Heapcheck:Leak
641     fun:absolutly
642     fun:brilliant
643     obj:condition
644     fun:detection
645     fun:expression
646   }"""
647
648   test_tsan_stack = """{
649     test
650     ThreadSanitizer:Race
651     fun:absolutly
652     fun:brilliant
653     obj:condition
654     fun:detection
655     fun:expression
656   }"""
657
658
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}",
670   ]
671
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}",
680   ]
681
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}",
689   ]
690
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}",
697   ]
698
699   positive_heapcheck_suppressions = [
700     "{\nzzz\nHeapcheck:Leak\n...\nobj:condition\n}",
701     "{\nzzz\nHeapcheck:Leak\nfun:absolutly\n}",
702   ]
703
704   positive_tsan_suppressions = [
705     "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}",
706     "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}",
707   ]
708
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}",
715   ]
716
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}",
725   ]
726
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}",
734   ]
735
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}",
745   ]
746
747   negative_heapcheck_suppressions = [
748     "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}",
749     "{\nzzz\nHeapcheck:Leak\nfun:brilliant\n}",
750   ]
751
752   negative_tsan_suppressions = [
753     "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}",
754     "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}",
755   ]
756
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)
773
774   # TODO(timurrrr): add TestFailPresubmit tests.
775
776   ### DrMemory self tests.
777
778   # http://crbug.com/96010 suppression.
779   stack_96010 = """{
780     UNADDRESSABLE ACCESS
781     name=<insert_a_suppression_name_here>
782     *!TestingProfile::FinishInit
783     *!TestingProfile::TestingProfile
784     *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
785     *!testing::Test::Run
786   }"""
787
788   suppress_96010 = [
789     "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n",
790     ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" +
791      "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"),
792     "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n",
793     "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n",
794     # No name should be needed
795     "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n",
796     # Whole trace
797     ("UNADDRESSABLE ACCESS\n" +
798      "*!TestingProfile::FinishInit\n" +
799      "*!TestingProfile::TestingProfile\n" +
800      "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
801      "*!testing::Test::Run\n"),
802   ]
803
804   negative_96010 = [
805     # Wrong type
806     "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
807     # No ellipsis
808     "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
809   ]
810
811   TestStack(stack_96010, suppress_96010, negative_96010,
812             suppression_parser=ReadDrMemorySuppressions)
813
814   # Invalid heap arg
815   stack_invalid = """{
816     INVALID HEAP ARGUMENT
817     name=asdf
818     *!foo
819   }"""
820   suppress_invalid = [
821     "INVALID HEAP ARGUMENT\n*!foo\n",
822   ]
823   negative_invalid = [
824     "UNADDRESSABLE ACCESS\n*!foo\n",
825   ]
826
827   TestStack(stack_invalid, suppress_invalid, negative_invalid,
828             suppression_parser=ReadDrMemorySuppressions)
829
830   # Suppress only ntdll
831   stack_in_ntdll = """{
832     UNADDRESSABLE ACCESS
833     name=<insert_a_suppression_name_here>
834     ntdll.dll!RtlTryEnterCriticalSection
835   }"""
836   stack_not_ntdll = """{
837     UNADDRESSABLE ACCESS
838     name=<insert_a_suppression_name_here>
839     notntdll.dll!RtlTryEnterCriticalSection
840   }"""
841
842   suppress_in_ntdll = [
843     "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
844   ]
845   suppress_in_any = [
846     "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
847   ]
848
849   TestStack(stack_in_ntdll, suppress_in_ntdll + suppress_in_any, [],
850             suppression_parser=ReadDrMemorySuppressions)
851   # Make sure we don't wildcard away the "not" part and match ntdll.dll by
852   # accident.
853   TestStack(stack_not_ntdll, suppress_in_any, suppress_in_ntdll,
854             suppression_parser=ReadDrMemorySuppressions)
855
856   # Suppress a POSSIBLE LEAK with LEAK.
857   stack_foo_possible = """{
858     POSSIBLE LEAK
859     name=foo possible
860     *!foo
861   }"""
862   suppress_foo_possible = [ "POSSIBLE LEAK\n*!foo\n" ]
863   suppress_foo_leak = [ "LEAK\n*!foo\n" ]
864   TestStack(stack_foo_possible, suppress_foo_possible + suppress_foo_leak, [],
865             suppression_parser=ReadDrMemorySuppressions)
866
867   # Don't suppress LEAK with POSSIBLE LEAK.
868   stack_foo_leak = """{
869     LEAK
870     name=foo leak
871     *!foo
872   }"""
873   TestStack(stack_foo_leak, suppress_foo_leak, suppress_foo_possible,
874             suppression_parser=ReadDrMemorySuppressions)
875
876   # Test case insensitivity of module names.
877   stack_user32_mixed_case = """{
878     LEAK
879     name=<insert>
880     USER32.dll!foo
881     user32.DLL!bar
882     user32.dll!baz
883   }"""
884   suppress_user32 = [  # Module name case doesn't matter.
885       "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n",
886       "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n",
887       ]
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",
891       ]
892   TestStack(stack_user32_mixed_case, suppress_user32, no_suppress_user32,
893             suppression_parser=ReadDrMemorySuppressions)
894
895   # Test mod!... frames.
896   stack_kernel32_through_ntdll = """{
897     LEAK
898     name=<insert>
899     kernel32.dll!foo
900     KERNEL32.dll!bar
901     kernel32.DLL!baz
902     ntdll.dll!quux
903   }"""
904   suppress_mod_ellipsis = [
905       "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
906       "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
907       ]
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",
911       ]
912   TestStack(stack_kernel32_through_ntdll, suppress_mod_ellipsis,
913             no_suppress_mod_ellipsis,
914             suppression_parser=ReadDrMemorySuppressions)
915
916   # Test that the presubmit checks work.
917   forgot_to_name = """
918     UNADDRESSABLE ACCESS
919     name=<insert_a_suppression_name_here>
920     ntdll.dll!RtlTryEnterCriticalSection
921   """
922   TestFailPresubmit(forgot_to_name, 'forgotten to put a suppression',
923                     suppression_parser=ReadDrMemorySuppressions)
924
925   named_twice = """
926     UNADDRESSABLE ACCESS
927     name=http://crbug.com/1234
928     *!foo
929
930     UNADDRESSABLE ACCESS
931     name=http://crbug.com/1234
932     *!bar
933   """
934   TestFailPresubmit(named_twice, 'defined more than once',
935                     suppression_parser=ReadDrMemorySuppressions)
936
937   forgot_stack = """
938     UNADDRESSABLE ACCESS
939     name=http://crbug.com/1234
940   """
941   TestFailPresubmit(forgot_stack, 'has no stack frames',
942                     suppression_parser=ReadDrMemorySuppressions)
943
944   ends_in_ellipsis = """
945     UNADDRESSABLE ACCESS
946     name=http://crbug.com/1234
947     ntdll.dll!RtlTryEnterCriticalSection
948     ...
949   """
950   TestFailPresubmit(ends_in_ellipsis, 'ends in an ellipsis',
951                     suppression_parser=ReadDrMemorySuppressions)
952
953   bad_stack_frame = """
954     UNADDRESSABLE ACCESS
955     name=http://crbug.com/1234
956     fun:memcheck_style_frame
957   """
958   TestFailPresubmit(bad_stack_frame, 'Unexpected stack frame pattern',
959                     suppression_parser=ReadDrMemorySuppressions)
960
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,
973   }
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))
980
981   # Test ValgrindStyleSuppression.__str__.
982   supp = ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
983                                   ["...", "fun:foo"], "supp.txt:1")
984   # Intentional 3-space indent.  =/
985   supp_str = ("{\n"
986               "   http://crbug.com/1234\n"
987               "   Memcheck:Leak\n"
988               "   ...\n"
989               "   fun:foo\n"
990               "}\n")
991   assert str(supp) == supp_str, (
992       "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
993
994   # Test DrMemorySuppression.__str__.
995   supp = DrMemorySuppression(
996       "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
997   supp_str = ("LEAK\n"
998               "name=http://crbug.com/1234\n"
999               "...\n"
1000               "*!foo\n")
1001   assert str(supp) == supp_str, (
1002       "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
1003
1004   supp = DrMemorySuppression(
1005       "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01",
1006       ["ntdll.dll!*", "*!foo"], "supp.txt:1")
1007   supp_str = ("UNINITIALIZED READ\n"
1008               "name=http://crbug.com/1234\n"
1009               "instruction=test 0x08(%eax) $0x01\n"
1010               "ntdll.dll!*\n"
1011               "*!foo\n")
1012   assert str(supp) == supp_str, (
1013       "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
1014
1015
1016 if __name__ == '__main__':
1017   SelfTest()
1018   print 'PASS'