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