| #!/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() |