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, "drmemory", "suppressions.txt") 69 result['drmem_suppressions'] = ReadSuppressionsFromFile(supp_filename) 70 supp_filename = JOIN(suppressions_root, "drmemory", "suppressions_full.txt") 71 result['drmem_full_suppressions'] = ReadSuppressionsFromFile(supp_filename) 72 73 return result 74 75 76def GlobToRegex(glob_pattern, ignore_case=False): 77 """Translate glob wildcards (*?) into regex syntax. Escape the rest.""" 78 regex = '' 79 for char in glob_pattern: 80 if char == '*': 81 regex += '.*' 82 elif char == '?': 83 regex += '.' 84 elif ignore_case and char.isalpha(): 85 regex += '[%s%s]' % (char.lower(), char.upper()) 86 else: 87 regex += re.escape(char) 88 return ''.join(regex) 89 90 91def StripAndSkipCommentsIterator(lines): 92 """Generator of (line_no, line) pairs that strips comments and whitespace.""" 93 for (line_no, line) in enumerate(lines): 94 line = line.strip() # Drop \n 95 if line.startswith('#'): 96 continue # Comments 97 # Skip comment lines, but not empty lines, they indicate the end of a 98 # suppression. Add one to the line number as well, since most editors use 99 # 1-based numberings, and enumerate is 0-based. 100 yield (line_no + 1, line) 101 102 103class Suppression(object): 104 """This class represents a single stack trace suppression. 105 106 Attributes: 107 description: A string representing the error description. 108 type: A string representing the error type, e.g. Memcheck:Leak. 109 stack: The lines comprising the stack trace for the suppression. 110 regex: The actual regex used to match against scraped reports. 111 """ 112 113 def __init__(self, description, type, stack, defined_at, regex): 114 """Inits Suppression. 115 116 description, type, stack, regex: same as class attributes 117 defined_at: file:line identifying where the suppression was defined 118 """ 119 self.description = description 120 self.type = type 121 self.stack = stack 122 self.defined_at = defined_at 123 self.regex = re.compile(regex, re.MULTILINE) 124 125 def Match(self, suppression_from_report): 126 """Returns bool indicating whether this suppression matches 127 the suppression generated from Valgrind error report. 128 129 We match our suppressions against generated suppressions 130 (not against reports) since they have the same format 131 while the reports are taken from XML, contain filenames, 132 they are demangled, and are generally more difficult to 133 parse. 134 135 Args: 136 suppression_from_report: list of strings (function names). 137 Returns: 138 True if the suppression is not empty and matches the report. 139 """ 140 if not self.stack: 141 return False 142 lines = [f.strip() for f in suppression_from_report] 143 return self.regex.match('\n'.join(lines) + '\n') is not None 144 145 146def FilenameToTool(filename): 147 """Return the name of the tool that a file is related to, or None. 148 149 Example mappings: 150 tools/valgrind/tsan/suppressions.txt -> tsan 151 tools/valgrind/drmemory/suppressions.txt -> drmemory 152 tools/valgrind/drmemory/suppressions_full.txt -> drmemory 153 tools/valgrind/memcheck/suppressions.txt -> memcheck 154 tools/valgrind/memcheck/suppressions_mac.txt -> memcheck 155 """ 156 filename = os.path.abspath(filename) 157 parts = filename.split(os.sep) 158 tool = parts[-2] 159 if tool in ('drmemory', 'memcheck', 'tsan'): 160 return tool 161 return None 162 163 164def ReadSuppressionsFromFile(filename): 165 """Read suppressions from the given file and return them as a list""" 166 tool_to_parser = { 167 "drmemory": ReadDrMemorySuppressions, 168 "memcheck": ReadValgrindStyleSuppressions, 169 "tsan": ReadValgrindStyleSuppressions, 170 } 171 tool = FilenameToTool(filename) 172 assert tool in tool_to_parser, ( 173 "unknown tool %s for filename %s" % (tool, filename)) 174 parse_func = tool_to_parser[tool] 175 176 # Consider non-existent files to be empty. 177 if not os.path.exists(filename): 178 return [] 179 180 input_file = file(filename, 'r') 181 try: 182 return parse_func(input_file, filename) 183 except SuppressionError: 184 input_file.close() 185 raise 186 187 188class ValgrindStyleSuppression(Suppression): 189 """A suppression using the Valgrind syntax. 190 191 Most tools, even ones that are not Valgrind-based, use this syntax, ie 192 TSan, etc. 193 194 Attributes: 195 Same as Suppression. 196 """ 197 198 def __init__(self, description, type, stack, defined_at): 199 """Creates a suppression using the Memcheck and TSan, syntax.""" 200 regex = '{\n.*\n%s\n' % type 201 for line in stack: 202 if line == ELLIPSIS: 203 regex += '(.*\n)*' 204 else: 205 regex += GlobToRegex(line) 206 regex += '\n' 207 regex += '(.*\n)*' 208 regex += '}' 209 210 # In the recent version of valgrind-variant we've switched 211 # from memcheck's default Addr[1248]/Value[1248]/Cond suppression types 212 # to simply Unaddressable/Uninitialized. 213 # The suppression generator no longer gives us "old" types thus 214 # for the "new-type" suppressions: 215 # * Memcheck:Unaddressable should also match Addr* reports, 216 # * Memcheck:Uninitialized should also match Cond and Value reports, 217 # 218 # We also want to support legacy suppressions (e.g. copied from 219 # upstream bugs etc), so: 220 # * Memcheck:Addr[1248] suppressions should match Unaddressable reports, 221 # * Memcheck:Cond and Memcheck:Value[1248] should match Uninitialized. 222 # Please note the latest two rules only apply to the 223 # tools/valgrind/waterfall.sh suppression matcher and the real 224 # valgrind-variant Memcheck will not suppress 225 # e.g. Addr1 printed as Unaddressable with Addr4 suppression. 226 # Be careful to check the access size while copying legacy suppressions! 227 for sz in [1, 2, 4, 8]: 228 regex = regex.replace("\nMemcheck:Addr%d\n" % sz, 229 "\nMemcheck:(Addr%d|Unaddressable)\n" % sz) 230 regex = regex.replace("\nMemcheck:Value%d\n" % sz, 231 "\nMemcheck:(Value%d|Uninitialized)\n" % sz) 232 regex = regex.replace("\nMemcheck:Cond\n", 233 "\nMemcheck:(Cond|Uninitialized)\n") 234 regex = regex.replace("\nMemcheck:Unaddressable\n", 235 "\nMemcheck:(Addr.|Unaddressable)\n") 236 regex = regex.replace("\nMemcheck:Uninitialized\n", 237 "\nMemcheck:(Cond|Value.|Uninitialized)\n") 238 239 return super(ValgrindStyleSuppression, self).__init__( 240 description, type, stack, defined_at, regex) 241 242 def __str__(self): 243 """Stringify.""" 244 lines = [self.description, self.type] + self.stack 245 return "{\n %s\n}\n" % "\n ".join(lines) 246 247 248class SuppressionError(Exception): 249 def __init__(self, message, happened_at): 250 self._message = message 251 self._happened_at = happened_at 252 253 def __str__(self): 254 return 'Error reading suppressions at %s!\n%s' % ( 255 self._happened_at, self._message) 256 257 258def ReadValgrindStyleSuppressions(lines, supp_descriptor): 259 """Given a list of lines, returns a list of suppressions. 260 261 Args: 262 lines: a list of lines containing suppressions. 263 supp_descriptor: should typically be a filename. 264 Used only when printing errors. 265 """ 266 result = [] 267 cur_descr = '' 268 cur_type = '' 269 cur_stack = [] 270 in_suppression = False 271 nline = 0 272 for line in lines: 273 nline += 1 274 line = line.strip() 275 if line.startswith('#'): 276 continue 277 if not in_suppression: 278 if not line: 279 # empty lines between suppressions 280 pass 281 elif line.startswith('{'): 282 in_suppression = True 283 pass 284 else: 285 raise SuppressionError('Expected: "{"', 286 "%s:%d" % (supp_descriptor, nline)) 287 elif line.startswith('}'): 288 result.append( 289 ValgrindStyleSuppression(cur_descr, cur_type, cur_stack, 290 "%s:%d" % (supp_descriptor, nline))) 291 cur_descr = '' 292 cur_type = '' 293 cur_stack = [] 294 in_suppression = False 295 elif not cur_descr: 296 cur_descr = line 297 continue 298 elif not cur_type: 299 if (not line.startswith("Memcheck:") and 300 not line.startswith("ThreadSanitizer:")): 301 raise SuppressionError( 302 'Expected "Memcheck:TYPE" or "ThreadSanitizer:TYPE", ' 303 'got "%s"' % line, 304 "%s:%d" % (supp_descriptor, nline)) 305 supp_type = line.split(':')[1] 306 if not supp_type in ["Addr1", "Addr2", "Addr4", "Addr8", 307 "Cond", "Free", "Jump", "Leak", "Overlap", "Param", 308 "Value1", "Value2", "Value4", "Value8", 309 "Race", "UnlockNonLocked", "InvalidLock", 310 "Unaddressable", "Uninitialized"]: 311 raise SuppressionError('Unknown suppression type "%s"' % supp_type, 312 "%s:%d" % (supp_descriptor, nline)) 313 cur_type = line 314 continue 315 elif re.match("^fun:.*|^obj:.*|^\.\.\.$", line): 316 cur_stack.append(line.strip()) 317 elif len(cur_stack) == 0 and cur_type == "Memcheck:Param": 318 cur_stack.append(line.strip()) 319 else: 320 raise SuppressionError( 321 '"fun:function_name" or "obj:object_file" or "..." expected', 322 "%s:%d" % (supp_descriptor, nline)) 323 return result 324 325 326def PresubmitCheckSuppressions(supps): 327 """Check a list of suppressions and return a list of SuppressionErrors. 328 329 Mostly useful for separating the checking logic from the Presubmit API for 330 testing. 331 """ 332 known_supp_names = {} # Key: name, Value: suppression. 333 errors = [] 334 for s in supps: 335 if re.search("<.*suppression.name.here>", s.description): 336 # Suppression name line is 337 # <insert_a_suppression_name_here> for Memcheck, 338 # <Put your suppression name here> for TSan, 339 # name=<insert_a_suppression_name_here> for DrMemory 340 errors.append( 341 SuppressionError( 342 "You've forgotten to put a suppression name like bug_XXX", 343 s.defined_at)) 344 continue 345 346 if s.description in known_supp_names: 347 errors.append( 348 SuppressionError( 349 'Suppression named "%s" is defined more than once, ' 350 'see %s' % (s.description, 351 known_supp_names[s.description].defined_at), 352 s.defined_at)) 353 else: 354 known_supp_names[s.description] = s 355 return errors 356 357 358def PresubmitCheck(input_api, output_api): 359 """A helper function useful in PRESUBMIT.py 360 Returns a list of errors or []. 361 """ 362 sup_regex = re.compile('suppressions.*\.txt$') 363 filenames = [f.AbsoluteLocalPath() for f in input_api.AffectedFiles() 364 if sup_regex.search(f.LocalPath())] 365 366 errors = [] 367 368 # TODO(timurrrr): warn on putting suppressions into a wrong file, 369 # e.g. TSan suppression in a memcheck file. 370 371 for f in filenames: 372 try: 373 supps = ReadSuppressionsFromFile(f) 374 errors.extend(PresubmitCheckSuppressions(supps)) 375 except SuppressionError as e: 376 errors.append(e) 377 378 return [output_api.PresubmitError(str(e)) for e in errors] 379 380 381class DrMemorySuppression(Suppression): 382 """A suppression using the DrMemory syntax. 383 384 Attributes: 385 instr: The instruction to match. 386 Rest inherited from Suppression. 387 """ 388 389 def __init__(self, name, report_type, instr, stack, defined_at): 390 """Constructor.""" 391 self.instr = instr 392 393 # Construct the regex. 394 regex = '{\n' 395 if report_type == 'LEAK': 396 regex += '(POSSIBLE )?LEAK' 397 else: 398 regex += report_type 399 regex += '\nname=.*\n' 400 401 # TODO(rnk): Implement http://crbug.com/107416#c5 . 402 # drmemory_analyze.py doesn't generate suppressions with an instruction in 403 # them, so these suppressions will always fail to match. We should override 404 # Match to fetch the instruction from the report and try to match against 405 # that. 406 if instr: 407 regex += 'instruction=%s\n' % GlobToRegex(instr) 408 409 for line in stack: 410 if line == ELLIPSIS: 411 regex += '(.*\n)*' 412 elif '!' in line: 413 (mod, func) = line.split('!') 414 if func == ELLIPSIS: # mod!ellipsis frame 415 regex += '(%s\!.*\n)+' % GlobToRegex(mod, ignore_case=True) 416 else: # mod!func frame 417 # Ignore case for the module match, but not the function match. 418 regex += '%s\!%s\n' % (GlobToRegex(mod, ignore_case=True), 419 GlobToRegex(func, ignore_case=False)) 420 else: 421 regex += GlobToRegex(line) 422 regex += '\n' 423 regex += '(.*\n)*' # Match anything left in the stack. 424 regex += '}' 425 return super(DrMemorySuppression, self).__init__(name, report_type, stack, 426 defined_at, regex) 427 428 def __str__(self): 429 """Stringify.""" 430 text = self.type + "\n" 431 if self.description: 432 text += "name=%s\n" % self.description 433 if self.instr: 434 text += "instruction=%s\n" % self.instr 435 text += "\n".join(self.stack) 436 text += "\n" 437 return text 438 439 440# Possible DrMemory error report types. Keep consistent with suppress_name 441# array in drmemory/drmemory/report.c. 442DRMEMORY_ERROR_TYPES = [ 443 'UNADDRESSABLE ACCESS', 444 'UNINITIALIZED READ', 445 'INVALID HEAP ARGUMENT', 446 'GDI USAGE ERROR', 447 'HANDLE LEAK', 448 'LEAK', 449 'POSSIBLE LEAK', 450 'WARNING', 451 ] 452 453 454# Regexes to match valid drmemory frames. 455DRMEMORY_FRAME_PATTERNS = [ 456 re.compile(r"^.*\!.*$"), # mod!func 457 re.compile(r"^.*!\.\.\.$"), # mod!ellipsis 458 re.compile(r"^\<.*\+0x.*\>$"), # <mod+0xoffs> 459 re.compile(r"^\<not in a module\>$"), 460 re.compile(r"^system call .*$"), 461 re.compile(r"^\*$"), # wildcard 462 re.compile(r"^\.\.\.$"), # ellipsis 463 ] 464 465 466def ReadDrMemorySuppressions(lines, supp_descriptor): 467 """Given a list of lines, returns a list of DrMemory suppressions. 468 469 Args: 470 lines: a list of lines containing suppressions. 471 supp_descriptor: should typically be a filename. 472 Used only when parsing errors happen. 473 """ 474 lines = StripAndSkipCommentsIterator(lines) 475 suppressions = [] 476 for (line_no, line) in lines: 477 if not line: 478 continue 479 if line not in DRMEMORY_ERROR_TYPES: 480 raise SuppressionError('Expected a DrMemory error type, ' 481 'found %r instead\n Valid error types: %s' % 482 (line, ' '.join(DRMEMORY_ERROR_TYPES)), 483 "%s:%d" % (supp_descriptor, line_no)) 484 485 # Suppression starts here. 486 report_type = line 487 name = '' 488 instr = None 489 stack = [] 490 defined_at = "%s:%d" % (supp_descriptor, line_no) 491 found_stack = False 492 for (line_no, line) in lines: 493 if not found_stack and line.startswith('name='): 494 name = line.replace('name=', '') 495 elif not found_stack and line.startswith('instruction='): 496 instr = line.replace('instruction=', '') 497 else: 498 # Unrecognized prefix indicates start of stack trace. 499 found_stack = True 500 if not line: 501 # Blank line means end of suppression. 502 break 503 if not any([regex.match(line) for regex in DRMEMORY_FRAME_PATTERNS]): 504 raise SuppressionError( 505 ('Unexpected stack frame pattern at line %d\n' + 506 'Frames should be one of the following:\n' + 507 ' module!function\n' + 508 ' module!...\n' + 509 ' <module+0xhexoffset>\n' + 510 ' <not in a module>\n' + 511 ' system call Name\n' + 512 ' *\n' + 513 ' ...\n') % line_no, defined_at) 514 stack.append(line) 515 516 if len(stack) == 0: # In case we hit EOF or blank without any stack frames. 517 raise SuppressionError('Suppression "%s" has no stack frames, ends at %d' 518 % (name, line_no), defined_at) 519 if stack[-1] == ELLIPSIS: 520 raise SuppressionError('Suppression "%s" ends in an ellipsis on line %d' % 521 (name, line_no), defined_at) 522 523 suppressions.append( 524 DrMemorySuppression(name, report_type, instr, stack, defined_at)) 525 526 return suppressions 527 528 529def ParseSuppressionOfType(lines, supp_descriptor, def_line_no, report_type): 530 """Parse the suppression starting on this line. 531 532 Suppressions start with a type, have an optional name and instruction, and a 533 stack trace that ends in a blank line. 534 """ 535 536 537 538def TestStack(stack, positive, negative, suppression_parser=None): 539 """A helper function for SelfTest() that checks a single stack. 540 541 Args: 542 stack: the stack to match the suppressions. 543 positive: the list of suppressions that must match the given stack. 544 negative: the list of suppressions that should not match. 545 suppression_parser: optional arg for the suppression parser, default is 546 ReadValgrindStyleSuppressions. 547 """ 548 if not suppression_parser: 549 suppression_parser = ReadValgrindStyleSuppressions 550 for supp in positive: 551 parsed = suppression_parser(supp.split("\n"), "positive_suppression") 552 assert parsed[0].Match(stack.split("\n")), ( 553 "Suppression:\n%s\ndidn't match stack:\n%s" % (supp, stack)) 554 for supp in negative: 555 parsed = suppression_parser(supp.split("\n"), "negative_suppression") 556 assert not parsed[0].Match(stack.split("\n")), ( 557 "Suppression:\n%s\ndid match stack:\n%s" % (supp, stack)) 558 559 560def TestFailPresubmit(supp_text, error_text, suppression_parser=None): 561 """A helper function for SelfTest() that verifies a presubmit check fires. 562 563 Args: 564 supp_text: suppression text to parse. 565 error_text: text of the presubmit error we expect to find. 566 suppression_parser: optional arg for the suppression parser, default is 567 ReadValgrindStyleSuppressions. 568 """ 569 if not suppression_parser: 570 suppression_parser = ReadValgrindStyleSuppressions 571 try: 572 supps = suppression_parser(supp_text.split("\n"), "<presubmit suppression>") 573 except SuppressionError, e: 574 # If parsing raised an exception, match the error text here. 575 assert error_text in str(e), ( 576 "presubmit text %r not in SuppressionError:\n%r" % 577 (error_text, str(e))) 578 else: 579 # Otherwise, run the presubmit checks over the supps. We expect a single 580 # error that has text matching error_text. 581 errors = PresubmitCheckSuppressions(supps) 582 assert len(errors) == 1, ( 583 "expected exactly one presubmit error, got:\n%s" % errors) 584 assert error_text in str(errors[0]), ( 585 "presubmit text %r not in SuppressionError:\n%r" % 586 (error_text, str(errors[0]))) 587 588 589def SelfTest(): 590 """Tests the Suppression.Match() capabilities.""" 591 592 test_memcheck_stack_1 = """{ 593 test 594 Memcheck:Leak 595 fun:absolutly 596 fun:brilliant 597 obj:condition 598 fun:detection 599 fun:expression 600 }""" 601 602 test_memcheck_stack_2 = """{ 603 test 604 Memcheck:Uninitialized 605 fun:absolutly 606 fun:brilliant 607 obj:condition 608 fun:detection 609 fun:expression 610 }""" 611 612 test_memcheck_stack_3 = """{ 613 test 614 Memcheck:Unaddressable 615 fun:absolutly 616 fun:brilliant 617 obj:condition 618 fun:detection 619 fun:expression 620 }""" 621 622 test_memcheck_stack_4 = """{ 623 test 624 Memcheck:Addr4 625 fun:absolutly 626 fun:brilliant 627 obj:condition 628 fun:detection 629 fun:expression 630 }""" 631 632 test_tsan_stack = """{ 633 test 634 ThreadSanitizer:Race 635 fun:absolutly 636 fun:brilliant 637 obj:condition 638 fun:detection 639 fun:expression 640 }""" 641 642 643 positive_memcheck_suppressions_1 = [ 644 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n}", 645 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n}", 646 "{\nzzz\nMemcheck:Leak\nfun:absolutly\nfun:brilliant\n}", 647 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\n}", 648 "{\nzzz\nMemcheck:Leak\n...\nfun:detection\n}", 649 "{\nzzz\nMemcheck:Leak\nfun:absolutly\n...\nfun:detection\n}", 650 "{\nzzz\nMemcheck:Leak\nfun:ab*ly\n...\nfun:detection\n}", 651 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\n}", 652 "{\nzzz\nMemcheck:Leak\n...\nobj:condition\nfun:detection\n}", 653 "{\nzzz\nMemcheck:Leak\n...\nfun:brilliant\nobj:condition\n}", 654 ] 655 656 positive_memcheck_suppressions_2 = [ 657 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}", 658 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*ly\n}", 659 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\nfun:brilliant\n}", 660 # Legacy suppression types 661 "{\nzzz\nMemcheck:Value1\n...\nfun:brilliant\n}", 662 "{\nzzz\nMemcheck:Cond\n...\nfun:detection\n}", 663 "{\nzzz\nMemcheck:Value8\nfun:absolutly\nfun:brilliant\n}", 664 ] 665 666 positive_memcheck_suppressions_3 = [ 667 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}", 668 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}", 669 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\nfun:brilliant\n}", 670 # Legacy suppression types 671 "{\nzzz\nMemcheck:Addr1\n...\nfun:brilliant\n}", 672 "{\nzzz\nMemcheck:Addr8\n...\nfun:detection\n}", 673 ] 674 675 positive_memcheck_suppressions_4 = [ 676 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\n}", 677 "{\nzzz\nMemcheck:Unaddressable\nfun:absolutly\n}", 678 "{\nzzz\nMemcheck:Addr4\nfun:absolutly\nfun:brilliant\n}", 679 "{\nzzz\nMemcheck:Unaddressable\n...\nfun:brilliant\n}", 680 "{\nzzz\nMemcheck:Addr4\n...\nfun:detection\n}", 681 ] 682 683 positive_tsan_suppressions = [ 684 "{\nzzz\nThreadSanitizer:Race\n...\nobj:condition\n}", 685 "{\nzzz\nThreadSanitizer:Race\nfun:absolutly\n}", 686 ] 687 688 negative_memcheck_suppressions_1 = [ 689 "{\nzzz\nMemcheck:Leak\nfun:abnormal\n}", 690 "{\nzzz\nMemcheck:Leak\nfun:ab*liant\n}", 691 "{\nzzz\nMemcheck:Leak\nfun:brilliant\n}", 692 "{\nzzz\nMemcheck:Leak\nobj:condition\n}", 693 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}", 694 ] 695 696 negative_memcheck_suppressions_2 = [ 697 "{\nzzz\nMemcheck:Cond\nfun:abnormal\n}", 698 "{\nzzz\nMemcheck:Value2\nfun:abnormal\n}", 699 "{\nzzz\nMemcheck:Uninitialized\nfun:ab*liant\n}", 700 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}", 701 "{\nzzz\nMemcheck:Leak\nobj:condition\n}", 702 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}", 703 "{\nzzz\nMemcheck:Unaddressable\nfun:brilliant\n}", 704 ] 705 706 negative_memcheck_suppressions_3 = [ 707 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}", 708 "{\nzzz\nMemcheck:Uninitialized\nfun:absolutly\n}", 709 "{\nzzz\nMemcheck:Addr2\nfun:ab*liant\n}", 710 "{\nzzz\nMemcheck:Value4\nfun:brilliant\n}", 711 "{\nzzz\nMemcheck:Leak\nobj:condition\n}", 712 "{\nzzz\nMemcheck:Addr8\nfun:brilliant\n}", 713 ] 714 715 negative_memcheck_suppressions_4 = [ 716 "{\nzzz\nMemcheck:Addr1\nfun:abnormal\n}", 717 "{\nzzz\nMemcheck:Addr4\nfun:abnormal\n}", 718 "{\nzzz\nMemcheck:Unaddressable\nfun:abnormal\n}", 719 "{\nzzz\nMemcheck:Addr1\nfun:absolutly\n}", 720 "{\nzzz\nMemcheck:Addr2\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 ] 725 726 negative_tsan_suppressions = [ 727 "{\nzzz\nThreadSanitizer:Leak\nfun:absolutly\n}", 728 "{\nzzz\nThreadSanitizer:Race\nfun:brilliant\n}", 729 ] 730 731 TestStack(test_memcheck_stack_1, 732 positive_memcheck_suppressions_1, 733 negative_memcheck_suppressions_1) 734 TestStack(test_memcheck_stack_2, 735 positive_memcheck_suppressions_2, 736 negative_memcheck_suppressions_2) 737 TestStack(test_memcheck_stack_3, 738 positive_memcheck_suppressions_3, 739 negative_memcheck_suppressions_3) 740 TestStack(test_memcheck_stack_4, 741 positive_memcheck_suppressions_4, 742 negative_memcheck_suppressions_4) 743 TestStack(test_tsan_stack, positive_tsan_suppressions, 744 negative_tsan_suppressions) 745 746 # TODO(timurrrr): add TestFailPresubmit tests. 747 748 ### DrMemory self tests. 749 750 # http://crbug.com/96010 suppression. 751 stack_96010 = """{ 752 UNADDRESSABLE ACCESS 753 name=<insert_a_suppression_name_here> 754 *!TestingProfile::FinishInit 755 *!TestingProfile::TestingProfile 756 *!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody 757 *!testing::Test::Run 758 }""" 759 760 suppress_96010 = [ 761 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!testing::Test::Run\n", 762 ("UNADDRESSABLE ACCESS\nname=zzz\n...\n" + 763 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n"), 764 "UNADDRESSABLE ACCESS\nname=zzz\n...\n*!BrowserAboutHandlerTest*\n", 765 "UNADDRESSABLE ACCESS\nname=zzz\n*!TestingProfile::FinishInit\n", 766 # No name should be needed 767 "UNADDRESSABLE ACCESS\n*!TestingProfile::FinishInit\n", 768 # Whole trace 769 ("UNADDRESSABLE ACCESS\n" + 770 "*!TestingProfile::FinishInit\n" + 771 "*!TestingProfile::TestingProfile\n" + 772 "*!BrowserAboutHandlerTest_WillHandleBrowserAboutURL_Test::TestBody\n" + 773 "*!testing::Test::Run\n"), 774 ] 775 776 negative_96010 = [ 777 # Wrong type 778 "UNINITIALIZED READ\nname=zzz\n*!TestingProfile::FinishInit\n", 779 # No ellipsis 780 "UNADDRESSABLE ACCESS\nname=zzz\n*!BrowserAboutHandlerTest*\n", 781 ] 782 783 TestStack(stack_96010, suppress_96010, negative_96010, 784 suppression_parser=ReadDrMemorySuppressions) 785 786 # Invalid heap arg 787 stack_invalid = """{ 788 INVALID HEAP ARGUMENT 789 name=asdf 790 *!foo 791 }""" 792 suppress_invalid = [ 793 "INVALID HEAP ARGUMENT\n*!foo\n", 794 ] 795 negative_invalid = [ 796 "UNADDRESSABLE ACCESS\n*!foo\n", 797 ] 798 799 TestStack(stack_invalid, suppress_invalid, negative_invalid, 800 suppression_parser=ReadDrMemorySuppressions) 801 802 # Suppress only ntdll 803 stack_in_ntdll = """{ 804 UNADDRESSABLE ACCESS 805 name=<insert_a_suppression_name_here> 806 ntdll.dll!RtlTryEnterCriticalSection 807 }""" 808 stack_not_ntdll = """{ 809 UNADDRESSABLE ACCESS 810 name=<insert_a_suppression_name_here> 811 notntdll.dll!RtlTryEnterCriticalSection 812 }""" 813 814 suppress_in_ntdll = [ 815 "UNADDRESSABLE ACCESS\nntdll.dll!RtlTryEnterCriticalSection\n", 816 ] 817 suppress_in_any = [ 818 "UNADDRESSABLE ACCESS\n*!RtlTryEnterCriticalSection\n", 819 ] 820 821 TestStack(stack_in_ntdll, suppress_in_ntdll + suppress_in_any, [], 822 suppression_parser=ReadDrMemorySuppressions) 823 # Make sure we don't wildcard away the "not" part and match ntdll.dll by 824 # accident. 825 TestStack(stack_not_ntdll, suppress_in_any, suppress_in_ntdll, 826 suppression_parser=ReadDrMemorySuppressions) 827 828 # Suppress a POSSIBLE LEAK with LEAK. 829 stack_foo_possible = """{ 830 POSSIBLE LEAK 831 name=foo possible 832 *!foo 833 }""" 834 suppress_foo_possible = [ "POSSIBLE LEAK\n*!foo\n" ] 835 suppress_foo_leak = [ "LEAK\n*!foo\n" ] 836 TestStack(stack_foo_possible, suppress_foo_possible + suppress_foo_leak, [], 837 suppression_parser=ReadDrMemorySuppressions) 838 839 # Don't suppress LEAK with POSSIBLE LEAK. 840 stack_foo_leak = """{ 841 LEAK 842 name=foo leak 843 *!foo 844 }""" 845 TestStack(stack_foo_leak, suppress_foo_leak, suppress_foo_possible, 846 suppression_parser=ReadDrMemorySuppressions) 847 848 # Test case insensitivity of module names. 849 stack_user32_mixed_case = """{ 850 LEAK 851 name=<insert> 852 USER32.dll!foo 853 user32.DLL!bar 854 user32.dll!baz 855 }""" 856 suppress_user32 = [ # Module name case doesn't matter. 857 "LEAK\nuser32.dll!foo\nuser32.dll!bar\nuser32.dll!baz\n", 858 "LEAK\nUSER32.DLL!foo\nUSER32.DLL!bar\nUSER32.DLL!baz\n", 859 ] 860 no_suppress_user32 = [ # Function name case matters. 861 "LEAK\nuser32.dll!FOO\nuser32.dll!BAR\nuser32.dll!BAZ\n", 862 "LEAK\nUSER32.DLL!FOO\nUSER32.DLL!BAR\nUSER32.DLL!BAZ\n", 863 ] 864 TestStack(stack_user32_mixed_case, suppress_user32, no_suppress_user32, 865 suppression_parser=ReadDrMemorySuppressions) 866 867 # Test mod!... frames. 868 stack_kernel32_through_ntdll = """{ 869 LEAK 870 name=<insert> 871 kernel32.dll!foo 872 KERNEL32.dll!bar 873 kernel32.DLL!baz 874 ntdll.dll!quux 875 }""" 876 suppress_mod_ellipsis = [ 877 "LEAK\nkernel32.dll!...\nntdll.dll!quux\n", 878 "LEAK\nKERNEL32.DLL!...\nntdll.dll!quux\n", 879 ] 880 no_suppress_mod_ellipsis = [ 881 # Need one or more matching frames, not zero, unlike regular ellipsis. 882 "LEAK\nuser32.dll!...\nkernel32.dll!...\nntdll.dll!quux\n", 883 ] 884 TestStack(stack_kernel32_through_ntdll, suppress_mod_ellipsis, 885 no_suppress_mod_ellipsis, 886 suppression_parser=ReadDrMemorySuppressions) 887 888 # Test that the presubmit checks work. 889 forgot_to_name = """ 890 UNADDRESSABLE ACCESS 891 name=<insert_a_suppression_name_here> 892 ntdll.dll!RtlTryEnterCriticalSection 893 """ 894 TestFailPresubmit(forgot_to_name, 'forgotten to put a suppression', 895 suppression_parser=ReadDrMemorySuppressions) 896 897 named_twice = """ 898 UNADDRESSABLE ACCESS 899 name=http://crbug.com/1234 900 *!foo 901 902 UNADDRESSABLE ACCESS 903 name=http://crbug.com/1234 904 *!bar 905 """ 906 TestFailPresubmit(named_twice, 'defined more than once', 907 suppression_parser=ReadDrMemorySuppressions) 908 909 forgot_stack = """ 910 UNADDRESSABLE ACCESS 911 name=http://crbug.com/1234 912 """ 913 TestFailPresubmit(forgot_stack, 'has no stack frames', 914 suppression_parser=ReadDrMemorySuppressions) 915 916 ends_in_ellipsis = """ 917 UNADDRESSABLE ACCESS 918 name=http://crbug.com/1234 919 ntdll.dll!RtlTryEnterCriticalSection 920 ... 921 """ 922 TestFailPresubmit(ends_in_ellipsis, 'ends in an ellipsis', 923 suppression_parser=ReadDrMemorySuppressions) 924 925 bad_stack_frame = """ 926 UNADDRESSABLE ACCESS 927 name=http://crbug.com/1234 928 fun:memcheck_style_frame 929 """ 930 TestFailPresubmit(bad_stack_frame, 'Unexpected stack frame pattern', 931 suppression_parser=ReadDrMemorySuppressions) 932 933 # Test FilenameToTool. 934 filenames_to_tools = { 935 "tools/valgrind/tsan/suppressions.txt": "tsan", 936 "tools/valgrind/drmemory/suppressions.txt": "drmemory", 937 "tools/valgrind/drmemory/suppressions_full.txt": "drmemory", 938 "tools/valgrind/memcheck/suppressions.txt": "memcheck", 939 "tools/valgrind/memcheck/suppressions_mac.txt": "memcheck", 940 "asdf/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck", 941 "foo/bar/baz/tools/valgrind/memcheck/suppressions_mac.txt": "memcheck", 942 "foo/bar/baz/tools/valgrind/suppressions.txt": None, 943 "tools/valgrind/suppressions.txt": None, 944 } 945 for (filename, expected_tool) in filenames_to_tools.items(): 946 filename.replace('/', os.sep) # Make the path look native. 947 tool = FilenameToTool(filename) 948 assert tool == expected_tool, ( 949 "failed to get expected tool for filename %r, expected %s, got %s" % 950 (filename, expected_tool, tool)) 951 952 # Test ValgrindStyleSuppression.__str__. 953 supp = ValgrindStyleSuppression("http://crbug.com/1234", "Memcheck:Leak", 954 ["...", "fun:foo"], "supp.txt:1") 955 # Intentional 3-space indent. =/ 956 supp_str = ("{\n" 957 " http://crbug.com/1234\n" 958 " Memcheck:Leak\n" 959 " ...\n" 960 " fun:foo\n" 961 "}\n") 962 assert str(supp) == supp_str, ( 963 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str)) 964 965 # Test DrMemorySuppression.__str__. 966 supp = DrMemorySuppression( 967 "http://crbug.com/1234", "LEAK", None, ["...", "*!foo"], "supp.txt:1") 968 supp_str = ("LEAK\n" 969 "name=http://crbug.com/1234\n" 970 "...\n" 971 "*!foo\n") 972 assert str(supp) == supp_str, ( 973 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str)) 974 975 supp = DrMemorySuppression( 976 "http://crbug.com/1234", "UNINITIALIZED READ", "test 0x08(%eax) $0x01", 977 ["ntdll.dll!*", "*!foo"], "supp.txt:1") 978 supp_str = ("UNINITIALIZED READ\n" 979 "name=http://crbug.com/1234\n" 980 "instruction=test 0x08(%eax) $0x01\n" 981 "ntdll.dll!*\n" 982 "*!foo\n") 983 assert str(supp) == supp_str, ( 984 "str(supp) != supp_str:\nleft: %s\nright: %s" % (str(supp), supp_str)) 985 986 987if __name__ == '__main__': 988 SelfTest() 989 print 'PASS' 990