171 lines
4.6 KiB
Python
171 lines
4.6 KiB
Python
|
#!/usr/bin/env python
|
||
|
# Copyright (c) 2011 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.
|
||
|
|
||
|
"""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()
|