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
10Suppressions 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
24If ran from the command line, suppressions.py does a self-test
25of the Suppression class.
26"""
27
28import os
29import re
30import sys
31
32sys.path.insert(0, os.path.join(os.path.dirname(__file__),
33                                '..', 'python', 'google'))
34import path_utils
35
36
37ELLIPSIS = '...'
38
39
40def 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
76def 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
91def 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
103class 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
146def 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
164def 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
188class 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
248class 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
258def 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
326def 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
358def 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
381class 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.
442DRMEMORY_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.
455DRMEMORY_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
466def 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
529def 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
538def 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
560def 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
589def 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
987if __name__ == '__main__':
988  SelfTest()
989  print 'PASS'
990