1 #!/usr/bin/env python 2 # Copyright (c) 2011 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 """Valgrind-style suppressions for heapchecker reports. 7 8 Suppressions are defined as follows: 9 10 # optional one-line comments anywhere in the suppressions file. 11 { 12 Toolname:Errortype 13 Short description of the error. 14 fun:function_name 15 fun:wildcarded_fun*_name 16 # an ellipsis wildcards zero or more functions in a stack. 17 ... 18 fun:some_other_function_name 19 } 20 21 Note that only a 'fun:' prefix is allowed, i.e. we can't suppress objects and 22 source files. 23 24 If ran from the command line, suppressions.py does a self-test of the 25 Suppression class. 26 """ 27 28 import re 29 30 ELLIPSIS = '...' 31 32 33 class Suppression(object): 34 """This class represents a single stack trace suppression. 35 36 Attributes: 37 type: A string representing the error type, e.g. Heapcheck:Leak. 38 description: A string representing the error description. 39 """ 40 41 def __init__(self, kind, description, stack): 42 """Inits Suppression. 43 44 stack is a list of function names and/or wildcards. 45 46 Args: 47 kind: 48 description: Same as class attributes. 49 stack: A list of strings. 50 """ 51 self.type = kind 52 self.description = description 53 self._stack = stack 54 re_line = '' 55 re_bucket = '' 56 for line in stack: 57 if line == ELLIPSIS: 58 re_line += re.escape(re_bucket) 59 re_bucket = '' 60 re_line += '(.*\n)*' 61 else: 62 for char in line: 63 if char == '*': 64 re_line += re.escape(re_bucket) 65 re_bucket = '' 66 re_line += '.*' 67 else: # there can't be any '\*'s in a stack trace 68 re_bucket += char 69 re_line += re.escape(re_bucket) 70 re_bucket = '' 71 re_line += '\n' 72 self._re = re.compile(re_line, re.MULTILINE) 73 74 def Match(self, report): 75 """Returns bool indicating whether the suppression matches the given report. 76 77 Args: 78 report: list of strings (function names). 79 Returns: 80 True if the suppression is not empty and matches the report. 81 """ 82 if not self._stack: 83 return False 84 if self._re.match('\n'.join(report) + '\n'): 85 return True 86 else: 87 return False 88 89 90 class SuppressionError(Exception): 91 def __init__(self, filename, line, report=''): 92 Exception.__init__(self, filename, line, report) 93 self._file = filename 94 self._line = line 95 self._report = report 96 97 def __str__(self): 98 return 'Error reading suppressions from "%s" (line %d): %s.' % ( 99 self._file, self._line, self._report) 100 101 102 def ReadSuppressionsFromFile(filename): 103 """Given a file, returns a list of suppressions.""" 104 input_file = file(filename, 'r') 105 result = [] 106 cur_descr = '' 107 cur_type = '' 108 cur_stack = [] 109 nline = 0 110 try: 111 for line in input_file: 112 nline += 1 113 line = line.strip() 114 if line.startswith('#'): 115 continue 116 elif line.startswith('{'): 117 pass 118 elif line.startswith('}'): 119 result.append(Suppression(cur_type, cur_descr, cur_stack)) 120 cur_descr = '' 121 cur_type = '' 122 cur_stack = [] 123 elif not cur_descr: 124 cur_descr = line 125 continue 126 elif not cur_type: 127 cur_type = line 128 continue 129 elif line.startswith('fun:'): 130 line = line[4:] 131 cur_stack.append(line.strip()) 132 elif line.startswith(ELLIPSIS): 133 cur_stack.append(ELLIPSIS) 134 else: 135 raise SuppressionError(filename, nline, 136 '"fun:function_name" or "..." expected') 137 except SuppressionError: 138 input_file.close() 139 raise 140 return result 141 142 143 def MatchTest(): 144 """Tests the Suppression.Match() capabilities.""" 145 146 def GenSupp(*lines): 147 return Suppression('', '', list(lines)) 148 empty = GenSupp() 149 assert not empty.Match([]) 150 assert not empty.Match(['foo', 'bar']) 151 asterisk = GenSupp('*bar') 152 assert asterisk.Match(['foobar', 'foobaz']) 153 assert not asterisk.Match(['foobaz', 'foobar']) 154 ellipsis = GenSupp('...', 'foo') 155 assert ellipsis.Match(['foo', 'bar']) 156 assert ellipsis.Match(['bar', 'baz', 'foo']) 157 assert not ellipsis.Match(['bar', 'baz', 'bah']) 158 mixed = GenSupp('...', 'foo*', 'function') 159 assert mixed.Match(['foobar', 'foobaz', 'function']) 160 assert not mixed.Match(['foobar', 'blah', 'function']) 161 at_and_dollar = GenSupp('foo@GLIBC', 'bar@NOCANCEL') 162 assert at_and_dollar.Match(['foo@GLIBC', 'bar@NOCANCEL']) 163 re_chars = GenSupp('.*') 164 assert re_chars.Match(['.foobar']) 165 assert not re_chars.Match(['foobar']) 166 print 'PASS' 167 168 169 if __name__ == '__main__': 170 MatchTest() 171