Upstream version 11.40.277.0
[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, "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)
72
73   return result
74
75
76 def GlobToRegex(glob_pattern, ignore_case=False):
77   """Translate glob wildcards (*?) into regex syntax.  Escape the rest."""
78   regex = ''
79   for char in glob_pattern:
80     if char == '*':
81       regex += '.*'
82     elif char == '?':
83       regex += '.'
84     elif ignore_case and char.isalpha():
85       regex += '[%s%s]' % (char.lower(), char.upper())
86     else:
87       regex += re.escape(char)
88   return ''.join(regex)
89
90
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('#'):
96       continue  # Comments
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)
101
102
103 class Suppression(object):
104   """This class represents a single stack trace suppression.
105
106   Attributes:
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.
111   """
112
113   def __init__(self, description, type, stack, defined_at, regex):
114     """Inits Suppression.
115
116     description, type, stack, regex: same as class attributes
117     defined_at: file:line identifying where the suppression was defined
118     """
119     self.description = description
120     self.type = type
121     self.stack = stack
122     self.defined_at = defined_at
123     self.regex = re.compile(regex, re.MULTILINE)
124
125   def Match(self, suppression_from_report):
126     """Returns bool indicating whether this suppression matches
127        the suppression generated from Valgrind error report.
128
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
133        parse.
134
135     Args:
136       suppression_from_report: list of strings (function names).
137     Returns:
138       True if the suppression is not empty and matches the report.
139     """
140     if not self.stack:
141       return False
142     lines = [f.strip() for f in suppression_from_report]
143     return self.regex.match('\n'.join(lines) + '\n') is not None
144
145
146 def FilenameToTool(filename):
147   """Return the name of the tool that a file is related to, or None.
148
149   Example mappings:
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
155   """
156   filename = os.path.abspath(filename)
157   parts = filename.split(os.sep)
158   tool = parts[-2]
159   if tool in ('drmemory', 'memcheck', 'tsan'):
160     return tool
161   return None
162
163
164 def ReadSuppressionsFromFile(filename):
165   """Read suppressions from the given file and return them as a list"""
166   tool_to_parser = {
167     "drmemory":  ReadDrMemorySuppressions,
168     "memcheck":  ReadValgrindStyleSuppressions,
169     "tsan":      ReadValgrindStyleSuppressions,
170   }
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]
175
176   # Consider non-existent files to be empty.
177   if not os.path.exists(filename):
178     return []
179
180   input_file = file(filename, 'r')
181   try:
182     return parse_func(input_file, filename)
183   except SuppressionError:
184     input_file.close()
185     raise
186
187
188 class ValgrindStyleSuppression(Suppression):
189   """A suppression using the Valgrind syntax.
190
191   Most tools, even ones that are not Valgrind-based, use this syntax, ie
192   TSan, etc.
193
194   Attributes:
195     Same as Suppression.
196   """
197
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
201     for line in stack:
202       if line == ELLIPSIS:
203         regex += '(.*\n)*'
204       else:
205         regex += GlobToRegex(line)
206         regex += '\n'
207     regex += '(.*\n)*'
208     regex += '}'
209
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,
217     #
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")
238
239     return super(ValgrindStyleSuppression, self).__init__(
240         description, type, stack, defined_at, regex)
241
242   def __str__(self):
243     """Stringify."""
244     lines = [self.description, self.type] + self.stack
245     return "{\n   %s\n}\n" % "\n   ".join(lines)
246
247
248 class SuppressionError(Exception):
249   def __init__(self, message, happened_at):
250     self._message = message
251     self._happened_at = happened_at
252
253   def __str__(self):
254     return 'Error reading suppressions at %s!\n%s' % (
255         self._happened_at, self._message)
256
257
258 def ReadValgrindStyleSuppressions(lines, supp_descriptor):
259   """Given a list of lines, returns a list of suppressions.
260
261   Args:
262     lines: a list of lines containing suppressions.
263     supp_descriptor: should typically be a filename.
264         Used only when printing errors.
265   """
266   result = []
267   cur_descr = ''
268   cur_type = ''
269   cur_stack = []
270   in_suppression = False
271   nline = 0
272   for line in lines:
273     nline += 1
274     line = line.strip()
275     if line.startswith('#'):
276       continue
277     if not in_suppression:
278       if not line:
279         # empty lines between suppressions
280         pass
281       elif line.startswith('{'):
282         in_suppression = True
283         pass
284       else:
285         raise SuppressionError('Expected: "{"',
286                                "%s:%d" % (supp_descriptor, nline))
287     elif line.startswith('}'):
288       result.append(
289           ValgrindStyleSuppression(cur_descr, cur_type, cur_stack,
290                                    "%s:%d" % (supp_descriptor, nline)))
291       cur_descr = ''
292       cur_type = ''
293       cur_stack = []
294       in_suppression = False
295     elif not cur_descr:
296       cur_descr = line
297       continue
298     elif not cur_type:
299       if (not line.startswith("Memcheck:") and
300           not line.startswith("ThreadSanitizer:")):
301         raise SuppressionError(
302             'Expected "Memcheck:TYPE" or "ThreadSanitizer:TYPE", '
303             'got "%s"' % line,
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))
313       cur_type = line
314       continue
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())
319     else:
320       raise SuppressionError(
321           '"fun:function_name" or "obj:object_file" or "..." expected',
322           "%s:%d" % (supp_descriptor, nline))
323   return result
324
325
326 def PresubmitCheckSuppressions(supps):
327   """Check a list of suppressions and return a list of SuppressionErrors.
328
329   Mostly useful for separating the checking logic from the Presubmit API for
330   testing.
331   """
332   known_supp_names = {}  # Key: name, Value: suppression.
333   errors = []
334   for s in supps:
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
340       errors.append(
341           SuppressionError(
342               "You've forgotten to put a suppression name like bug_XXX",
343               s.defined_at))
344       continue
345
346     if s.description in known_supp_names:
347       errors.append(
348           SuppressionError(
349               'Suppression named "%s" is defined more than once, '
350               'see %s' % (s.description,
351                           known_supp_names[s.description].defined_at),
352               s.defined_at))
353     else:
354       known_supp_names[s.description] = s
355   return errors
356
357
358 def PresubmitCheck(input_api, output_api):
359   """A helper function useful in PRESUBMIT.py
360      Returns a list of errors or [].
361   """
362   sup_regex = re.compile('suppressions.*\.txt$')
363   filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles()
364                    if sup_regex.search(f.LocalPath())]
365
366   errors = []
367
368   # TODO(timurrrr): warn on putting suppressions into a wrong file,
369   # e.g. TSan suppression in a memcheck file.
370
371   for f in filenames:
372     try:
373       supps = ReadSuppressionsFromFile(f)
374       errors.extend(PresubmitCheckSuppressions(supps))
375     except SuppressionError as e:
376       errors.append(e)
377
378   return [output_api.PresubmitError(str(e)) for e in errors]
379
380
381 class DrMemorySuppression(Suppression):
382   """A suppression using the DrMemory syntax.
383
384   Attributes:
385     instr: The instruction to match.
386     Rest inherited from Suppression.
387   """
388
389   def __init__(self, name, report_type, instr, stack, defined_at):
390     """Constructor."""
391     self.instr = instr
392
393     # Construct the regex.
394     regex = '{\n'
395     if report_type == 'LEAK':
396       regex += '(POSSIBLE )?LEAK'
397     else:
398       regex += report_type
399     regex += '\nname=.*\n'
400
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
405     # that.
406     if instr:
407       regex += 'instruction=%s\n' % GlobToRegex(instr)
408
409     for line in stack:
410       if line == ELLIPSIS:
411         regex += '(.*\n)*'
412       elif '!' in line:
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))
420       else:
421         regex += GlobToRegex(line)
422         regex += '\n'
423     regex += '(.*\n)*'  # Match anything left in the stack.
424     regex += '}'
425     return super(DrMemorySuppression, self).__init__(name, report_type, stack,
426                                                      defined_at, regex)
427
428   def __str__(self):
429     """Stringify."""
430     text = self.type + "\n"
431     if self.description:
432       text += "name=%s\n" % self.description
433     if self.instr:
434       text += "instruction=%s\n" % self.instr
435     text += "\n".join(self.stack)
436     text += "\n"
437     return text
438
439
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',
446     'GDI USAGE ERROR',
447     'HANDLE LEAK',
448     'LEAK',
449     'POSSIBLE LEAK',
450     'WARNING',
451     ]
452
453
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
463     ]
464
465
466 def ReadDrMemorySuppressions(lines, supp_descriptor):
467   """Given a list of lines, returns a list of DrMemory suppressions.
468
469   Args:
470     lines: a list of lines containing suppressions.
471     supp_descriptor: should typically be a filename.
472       Used only when parsing errors happen.
473   """
474   lines = StripAndSkipCommentsIterator(lines)
475   suppressions = []
476   for (line_no, line) in lines:
477     if not line:
478       continue
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))
484
485     # Suppression starts here.
486     report_type = line
487     name = ''
488     instr = None
489     stack = []
490     defined_at = "%s:%d" % (supp_descriptor, line_no)
491     found_stack = False
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=', '')
497       else:
498         # Unrecognized prefix indicates start of stack trace.
499         found_stack = True
500         if not line:
501           # Blank line means end of suppression.
502           break
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' +
508                ' module!...\n' +
509                ' <module+0xhexoffset>\n' +
510                ' <not in a module>\n' +
511                ' system call Name\n' +
512                ' *\n' +
513                ' ...\n') % line_no, defined_at)
514         stack.append(line)
515
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)
522
523     suppressions.append(
524         DrMemorySuppression(name, report_type, instr, stack, defined_at))
525
526   return suppressions
527
528
529 def ParseSuppressionOfType(lines, supp_descriptor, def_line_no, report_type):
530   """Parse the suppression starting on this line.
531
532   Suppressions start with a type, have an optional name and instruction, and a
533   stack trace that ends in a blank line.
534   """
535
536
537
538 def TestStack(stack, positive, negative, suppression_parser=None):
539   """A helper function for SelfTest() that checks a single stack.
540
541   Args:
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.
547   """
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))
558
559
560 def TestFailPresubmit(supp_text, error_text, suppression_parser=None):
561   """A helper function for SelfTest() that verifies a presubmit check fires.
562
563   Args:
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.
568   """
569   if not suppression_parser:
570     suppression_parser = ReadValgrindStyleSuppressions
571   try:
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)))
578   else:
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])))
587
588
589 def SelfTest():
590   """Tests the Suppression.Match() capabilities."""
591
592   test_memcheck_stack_1 = """{
593     test
594     Memcheck:Leak
595     fun:absolutly
596     fun:brilliant
597     obj:condition
598     fun:detection
599     fun:expression
600   }"""
601
602   test_memcheck_stack_2 = """{
603     test
604     Memcheck:Uninitialized
605     fun:absolutly
606     fun:brilliant
607     obj:condition
608     fun:detection
609     fun:expression
610   }"""
611
612   test_memcheck_stack_3 = """{
613     test
614     Memcheck:Unaddressable
615     fun:absolutly
616     fun:brilliant
617     obj:condition
618     fun:detection
619     fun:expression
620   }"""
621
622   test_memcheck_stack_4 = """{
623     test
624     Memcheck:Addr4
625     fun:absolutly
626     fun:brilliant
627     obj:condition
628     fun:detection
629     fun:expression
630   }"""
631
632   test_tsan_stack = """{
633     test
634     ThreadSanitizer:Race
635     fun:absolutly
636     fun:brilliant
637     obj:condition
638     fun:detection
639     fun:expression
640   }"""
641
642
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}",
654   ]
655
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}",
664   ]
665
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}",
673   ]
674
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}",
681   ]
682
683   positive_tsan_suppressions = [
684     "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}",
685     "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}",
686   ]
687
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}",
694   ]
695
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}",
704   ]
705
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}",
713   ]
714
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}",
724   ]
725
726   negative_tsan_suppressions = [
727     "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}",
728     "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}",
729   ]
730
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)
745
746   # TODO(timurrrr): add TestFailPresubmit tests.
747
748   ### DrMemory self tests.
749
750   # http://crbug.com/96010 suppression.
751   stack_96010 = """{
752     UNADDRESSABLE ACCESS
753     name=<insert_a_suppression_name_here>
754     *!TestingProfile::FinishInit
755     *!TestingProfile::TestingProfile
756     *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody
757     *!testing::Test::Run
758   }"""
759
760   suppress_96010 = [
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",
768     # Whole trace
769     ("UNADDRESSABLE ACCESS\n" +
770      "*!TestingProfile::FinishInit\n" +
771      "*!TestingProfile::TestingProfile\n" +
772      "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" +
773      "*!testing::Test::Run\n"),
774   ]
775
776   negative_96010 = [
777     # Wrong type
778     "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n",
779     # No ellipsis
780     "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n",
781   ]
782
783   TestStack(stack_96010, suppress_96010, negative_96010,
784             suppression_parser=ReadDrMemorySuppressions)
785
786   # Invalid heap arg
787   stack_invalid = """{
788     INVALID HEAP ARGUMENT
789     name=asdf
790     *!foo
791   }"""
792   suppress_invalid = [
793     "INVALID HEAP ARGUMENT\n*!foo\n",
794   ]
795   negative_invalid = [
796     "UNADDRESSABLE ACCESS\n*!foo\n",
797   ]
798
799   TestStack(stack_invalid, suppress_invalid, negative_invalid,
800             suppression_parser=ReadDrMemorySuppressions)
801
802   # Suppress only ntdll
803   stack_in_ntdll = """{
804     UNADDRESSABLE ACCESS
805     name=<insert_a_suppression_name_here>
806     ntdll.dll!RtlTryEnterCriticalSection
807   }"""
808   stack_not_ntdll = """{
809     UNADDRESSABLE ACCESS
810     name=<insert_a_suppression_name_here>
811     notntdll.dll!RtlTryEnterCriticalSection
812   }"""
813
814   suppress_in_ntdll = [
815     "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n",
816   ]
817   suppress_in_any = [
818     "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n",
819   ]
820
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
824   # accident.
825   TestStack(stack_not_ntdll, suppress_in_any, suppress_in_ntdll,
826             suppression_parser=ReadDrMemorySuppressions)
827
828   # Suppress a POSSIBLE LEAK with LEAK.
829   stack_foo_possible = """{
830     POSSIBLE LEAK
831     name=foo possible
832     *!foo
833   }"""
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)
838
839   # Don't suppress LEAK with POSSIBLE LEAK.
840   stack_foo_leak = """{
841     LEAK
842     name=foo leak
843     *!foo
844   }"""
845   TestStack(stack_foo_leak, suppress_foo_leak, suppress_foo_possible,
846             suppression_parser=ReadDrMemorySuppressions)
847
848   # Test case insensitivity of module names.
849   stack_user32_mixed_case = """{
850     LEAK
851     name=<insert>
852     USER32.dll!foo
853     user32.DLL!bar
854     user32.dll!baz
855   }"""
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",
859       ]
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",
863       ]
864   TestStack(stack_user32_mixed_case, suppress_user32, no_suppress_user32,
865             suppression_parser=ReadDrMemorySuppressions)
866
867   # Test mod!... frames.
868   stack_kernel32_through_ntdll = """{
869     LEAK
870     name=<insert>
871     kernel32.dll!foo
872     KERNEL32.dll!bar
873     kernel32.DLL!baz
874     ntdll.dll!quux
875   }"""
876   suppress_mod_ellipsis = [
877       "LEAK\nkernel32.dll!...\nntdll.dll!quux\n",
878       "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n",
879       ]
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",
883       ]
884   TestStack(stack_kernel32_through_ntdll, suppress_mod_ellipsis,
885             no_suppress_mod_ellipsis,
886             suppression_parser=ReadDrMemorySuppressions)
887
888   # Test that the presubmit checks work.
889   forgot_to_name = """
890     UNADDRESSABLE ACCESS
891     name=<insert_a_suppression_name_here>
892     ntdll.dll!RtlTryEnterCriticalSection
893   """
894   TestFailPresubmit(forgot_to_name, 'forgotten to put a suppression',
895                     suppression_parser=ReadDrMemorySuppressions)
896
897   named_twice = """
898     UNADDRESSABLE ACCESS
899     name=http://crbug.com/1234
900     *!foo
901
902     UNADDRESSABLE ACCESS
903     name=http://crbug.com/1234
904     *!bar
905   """
906   TestFailPresubmit(named_twice, 'defined more than once',
907                     suppression_parser=ReadDrMemorySuppressions)
908
909   forgot_stack = """
910     UNADDRESSABLE ACCESS
911     name=http://crbug.com/1234
912   """
913   TestFailPresubmit(forgot_stack, 'has no stack frames',
914                     suppression_parser=ReadDrMemorySuppressions)
915
916   ends_in_ellipsis = """
917     UNADDRESSABLE ACCESS
918     name=http://crbug.com/1234
919     ntdll.dll!RtlTryEnterCriticalSection
920     ...
921   """
922   TestFailPresubmit(ends_in_ellipsis, 'ends in an ellipsis',
923                     suppression_parser=ReadDrMemorySuppressions)
924
925   bad_stack_frame = """
926     UNADDRESSABLE ACCESS
927     name=http://crbug.com/1234
928     fun:memcheck_style_frame
929   """
930   TestFailPresubmit(bad_stack_frame, 'Unexpected stack frame pattern',
931                     suppression_parser=ReadDrMemorySuppressions)
932
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,
944   }
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))
951
952   # Test ValgrindStyleSuppression.__str__.
953   supp = ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak",
954                                   ["...", "fun:foo"], "supp.txt:1")
955   # Intentional 3-space indent.  =/
956   supp_str = ("{\n"
957               "   http://crbug.com/1234\n"
958               "   Memcheck:Leak\n"
959               "   ...\n"
960               "   fun:foo\n"
961               "}\n")
962   assert str(supp) == supp_str, (
963       "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
964
965   # Test DrMemorySuppression.__str__.
966   supp = DrMemorySuppression(
967       "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1")
968   supp_str = ("LEAK\n"
969               "name=http://crbug.com/1234\n"
970               "...\n"
971               "*!foo\n")
972   assert str(supp) == supp_str, (
973       "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
974
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"
981               "ntdll.dll!*\n"
982               "*!foo\n")
983   assert str(supp) == supp_str, (
984       "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str))
985
986
987 if __name__ == '__main__':
988   SelfTest()
989   print 'PASS'