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, "..", "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
79def 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
94def 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
106class 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
149def 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
168def 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
193class 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
253class 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
263def 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
332def 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
364def 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
387class 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.
448DRMEMORY_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.
461DRMEMORY_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
472def 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
535def 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
544def 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
566def 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
595def 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
1016if __name__ == '__main__':
1017  SelfTest()
1018  print 'PASS'
1019