blob: 91c523816819e29d458a3ca7d0f002a2fb9a3496 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# suppressions.py
"""Valgrind-style suppressions for heapchecker reports.
Suppressions are defined as follows:
# optional one-line comments anywhere in the suppressions file.
{
Toolname:Errortype
Short description of the error.
fun:function_name
fun:wildcarded_fun*_name
# an ellipsis wildcards zero or more functions in a stack.
...
fun:some_other_function_name
}
Note that only a 'fun:' prefix is allowed, i.e. we can't suppress objects and
source files.
If ran from the command line, suppressions.py does a self-test of the
Suppression class.
"""
import re
ELLIPSIS = '...'
class Suppression(object):
"""This class represents a single stack trace suppression.
Attributes:
type: A string representing the error type, e.g. Heapcheck:Leak.
description: A string representing the error description.
"""
def __init__(self, kind, description, stack):
"""Inits Suppression.
stack is a list of function names and/or wildcards.
Args:
kind:
description: Same as class attributes.
stack: A list of strings.
"""
self.type = kind
self.description = description
self._stack = stack
re_line = ''
re_bucket = ''
for line in stack:
if line == ELLIPSIS:
re_line += re.escape(re_bucket)
re_bucket = ''
re_line += '(.*\n)*'
else:
for char in line:
if char == '*':
re_line += re.escape(re_bucket)
re_bucket = ''
re_line += '.*'
else: # there can't be any '\*'s in a stack trace
re_bucket += char
re_line += re.escape(re_bucket)
re_bucket = ''
re_line += '\n'
self._re = re.compile(re_line, re.MULTILINE)
def Match(self, report):
"""Returns bool indicating whether the suppression matches the given report.
Args:
report: list of strings (function names).
Returns:
True if the suppression is not empty and matches the report.
"""
if not self._stack:
return False
if self._re.match('\n'.join(report) + '\n'):
return True
else:
return False
class SuppressionError(Exception):
def __init__(self, filename, line, report=''):
Exception.__init__(self, filename, line, report)
self._file = filename
self._line = line
self._report = report
def __str__(self):
return 'Error reading suppressions from "%s" (line %d): %s.' % (
self._file, self._line, self._report)
def ReadSuppressionsFromFile(filename):
"""Given a file, returns a list of suppressions."""
input_file = file(filename, 'r')
result = []
cur_descr = ''
cur_type = ''
cur_stack = []
nline = 0
try:
for line in input_file:
nline += 1
line = line.strip()
if line.startswith('#'):
continue
elif line.startswith('{'):
pass
elif line.startswith('}'):
result.append(Suppression(cur_type, cur_descr, cur_stack))
cur_descr = ''
cur_type = ''
cur_stack = []
elif not cur_descr:
cur_descr = line
continue
elif not cur_type:
cur_type = line
continue
elif line.startswith('fun:'):
line = line[4:]
cur_stack.append(line.strip())
elif line.startswith(ELLIPSIS):
cur_stack.append(ELLIPSIS)
else:
raise SuppressionError(filename, nline,
'"fun:function_name" or "..." expected')
except SuppressionError:
input_file.close()
raise
return result
def MatchTest():
"""Tests the Suppression.Match() capabilities."""
def GenSupp(*lines):
return Suppression('', '', list(lines))
empty = GenSupp()
assert not empty.Match([])
assert not empty.Match(['foo', 'bar'])
asterisk = GenSupp('*bar')
assert asterisk.Match(['foobar', 'foobaz'])
assert not asterisk.Match(['foobaz', 'foobar'])
ellipsis = GenSupp('...', 'foo')
assert ellipsis.Match(['foo', 'bar'])
assert ellipsis.Match(['bar', 'baz', 'foo'])
assert not ellipsis.Match(['bar', 'baz', 'bah'])
mixed = GenSupp('...', 'foo*', 'function')
assert mixed.Match(['foobar', 'foobaz', 'function'])
assert not mixed.Match(['foobar', 'blah', 'function'])
at_and_dollar = GenSupp('foo@GLIBC', 'bar@NOCANCEL')
assert at_and_dollar.Match(['foo@GLIBC', 'bar@NOCANCEL'])
re_chars = GenSupp('.*')
assert re_chars.Match(['.foobar'])
assert not re_chars.Match(['foobar'])
print 'PASS'
if __name__ == '__main__':
MatchTest()