Home | History | Annotate | Download | only in valgrind
      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'
   1019