| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # memcheck_analyze.py | 6 # memcheck_analyze.py |
| 7 | 7 |
| 8 ''' Given a valgrind XML file, parses errors and uniques them.''' | 8 ''' Given a valgrind XML file, parses errors and uniques them.''' |
| 9 | 9 |
| 10 import gdb_helper | 10 import gdb_helper |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 108 # Try using gdb | 108 # Try using gdb |
| 109 TheAddressTable.Add(frame_dict[OBJECT_FILE], | 109 TheAddressTable.Add(frame_dict[OBJECT_FILE], |
| 110 frame_dict[INSTRUCTION_POINTER]) | 110 frame_dict[INSTRUCTION_POINTER]) |
| 111 return frames | 111 return frames |
| 112 | 112 |
| 113 class ValgrindError: | 113 class ValgrindError: |
| 114 ''' Takes a <DOM Element: error> node and reads all the data from it. A | 114 ''' Takes a <DOM Element: error> node and reads all the data from it. A |
| 115 ValgrindError is immutable and is hashed on its pretty printed output. | 115 ValgrindError is immutable and is hashed on its pretty printed output. |
| 116 ''' | 116 ''' |
| 117 | 117 |
| 118 def __init__(self, source_dir, error_node, commandline): | 118 def __init__(self, source_dir, error_node, commandline, testcase): |
| 119 ''' Copies all the relevant information out of the DOM and into object | 119 ''' Copies all the relevant information out of the DOM and into object |
| 120 properties. | 120 properties. |
| 121 | 121 |
| 122 Args: | 122 Args: |
| 123 error_node: The <error></error> DOM node we're extracting from. | 123 error_node: The <error></error> DOM node we're extracting from. |
| 124 source_dir: Prefix that should be stripped from the <dir> node. | 124 source_dir: Prefix that should be stripped from the <dir> node. |
| 125 commandline: The command that was run under valgrind | 125 commandline: The command that was run under valgrind |
| 126 testcase: The test case name, if known. |
| 126 ''' | 127 ''' |
| 127 | 128 |
| 128 # Valgrind errors contain one <what><stack> pair, plus an optional | 129 # Valgrind errors contain one <what><stack> pair, plus an optional |
| 129 # <auxwhat><stack> pair, plus an optional <origin><what><stack></origin>, | 130 # <auxwhat><stack> pair, plus an optional <origin><what><stack></origin>, |
| 130 # plus (since 3.5.0) a <suppression></suppression> pair. | 131 # plus (since 3.5.0) a <suppression></suppression> pair. |
| 131 # (Origin is nicely enclosed; too bad the other two aren't.) | 132 # (Origin is nicely enclosed; too bad the other two aren't.) |
| 132 # The most common way to see all three in one report is | 133 # The most common way to see all three in one report is |
| 133 # a syscall with a parameter that points to uninitialized memory, e.g. | 134 # a syscall with a parameter that points to uninitialized memory, e.g. |
| 134 # Format: | 135 # Format: |
| 135 # <error> | 136 # <error> |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 187 # <file>gtest-internal-inl.h</file> | 188 # <file>gtest-internal-inl.h</file> |
| 188 # <line>655</line> | 189 # <line>655</line> |
| 189 # </frame> | 190 # </frame> |
| 190 # although the dir, file, and line elements are missing if there is | 191 # although the dir, file, and line elements are missing if there is |
| 191 # no debug info. | 192 # no debug info. |
| 192 | 193 |
| 193 self._kind = getTextOf(error_node, "kind") | 194 self._kind = getTextOf(error_node, "kind") |
| 194 self._backtraces = [] | 195 self._backtraces = [] |
| 195 self._suppression = None | 196 self._suppression = None |
| 196 self._commandline = commandline | 197 self._commandline = commandline |
| 198 self._testcase = testcase |
| 197 | 199 |
| 198 # Iterate through the nodes, parsing <what|auxwhat><stack> pairs. | 200 # Iterate through the nodes, parsing <what|auxwhat><stack> pairs. |
| 199 description = None | 201 description = None |
| 200 for node in error_node.childNodes: | 202 for node in error_node.childNodes: |
| 201 if node.localName == "what" or node.localName == "auxwhat": | 203 if node.localName == "what" or node.localName == "auxwhat": |
| 202 description = "".join([n.data for n in node.childNodes | 204 description = "".join([n.data for n in node.childNodes |
| 203 if n.nodeType == n.TEXT_NODE]) | 205 if n.nodeType == n.TEXT_NODE]) |
| 204 elif node.localName == "xwhat": | 206 elif node.localName == "xwhat": |
| 205 description = getTextOf(node, "text") | 207 description = getTextOf(node, "text") |
| 206 elif node.localName == "stack": | 208 elif node.localName == "stack": |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 253 elif frame[SRC_FILE_DIR] != "": | 255 elif frame[SRC_FILE_DIR] != "": |
| 254 output += (" (" + frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_NAME] + | 256 output += (" (" + frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_NAME] + |
| 255 ":" + frame[SRC_LINE] + ")") | 257 ":" + frame[SRC_LINE] + ")") |
| 256 else: | 258 else: |
| 257 output += " (" + frame[OBJECT_FILE] + ")" | 259 output += " (" + frame[OBJECT_FILE] + ")" |
| 258 output += "\n" | 260 output += "\n" |
| 259 | 261 |
| 260 assert self._suppression != None, "Your Valgrind doesn't generate " \ | 262 assert self._suppression != None, "Your Valgrind doesn't generate " \ |
| 261 "suppressions - is it too old?" | 263 "suppressions - is it too old?" |
| 262 | 264 |
| 265 if self._testcase: |
| 266 output += "The report came from the `%s` test.\n" % self._testcase |
| 263 output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash() | 267 output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash() |
| 264 output += (" For more info on using suppressions see " | 268 output += (" For more info on using suppressions see " |
| 265 "http://dev.chromium.org/developers/how-tos/using-valgrind#TOC-Su
ppressing-Errors") | 269 "http://dev.chromium.org/developers/how-tos/using-valgrind#TOC-Su
ppressing-Errors") |
| 266 | 270 |
| 267 # Widen suppression slightly to make portable between mac and linux | 271 # Widen suppression slightly to make portable between mac and linux |
| 268 supp = self._suppression; | 272 supp = self._suppression; |
| 269 supp = supp.replace("fun:_Znwj", "fun:_Znw*") | 273 supp = supp.replace("fun:_Znwj", "fun:_Znw*") |
| 270 supp = supp.replace("fun:_Znwm", "fun:_Znw*") | 274 supp = supp.replace("fun:_Znwm", "fun:_Znw*") |
| 271 supp = supp.replace("fun:_Znaj", "fun:_Zna*") | 275 supp = supp.replace("fun:_Znaj", "fun:_Zna*") |
| 272 supp = supp.replace("fun:_Znam", "fun:_Zna*") | 276 supp = supp.replace("fun:_Znam", "fun:_Zna*") |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 391 self._use_gdb = use_gdb | 395 self._use_gdb = use_gdb |
| 392 | 396 |
| 393 # Contains the set of unique errors | 397 # Contains the set of unique errors |
| 394 self._errors = set() | 398 self._errors = set() |
| 395 | 399 |
| 396 # Contains the time when the we started analyzing the first log file. | 400 # Contains the time when the we started analyzing the first log file. |
| 397 # This variable is used to skip incomplete logs after some timeout. | 401 # This variable is used to skip incomplete logs after some timeout. |
| 398 self._analyze_start_time = None | 402 self._analyze_start_time = None |
| 399 | 403 |
| 400 | 404 |
| 401 def Report(self, files, check_sanity=False): | 405 def Report(self, files, testcase, check_sanity=False): |
| 402 '''Reads in a set of files and prints Memcheck report. | 406 '''Reads in a set of files and prints Memcheck report. |
| 403 | 407 |
| 404 Args: | 408 Args: |
| 405 files: A list of filenames. | 409 files: A list of filenames. |
| 406 check_sanity: if true, search for SANITY_TEST_SUPPRESSIONS | 410 check_sanity: if true, search for SANITY_TEST_SUPPRESSIONS |
| 407 ''' | 411 ''' |
| 408 # Beyond the detailed errors parsed by ValgrindError above, | 412 # Beyond the detailed errors parsed by ValgrindError above, |
| 409 # the xml file contain records describing suppressions that were used: | 413 # the xml file contain records describing suppressions that were used: |
| 410 # <suppcounts> | 414 # <suppcounts> |
| 411 # <pair> | 415 # <pair> |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 517 for x in node.childNodes: | 521 for x in node.childNodes: |
| 518 if x.nodeType == node.TEXT_NODE and "Command" in x.data: | 522 if x.nodeType == node.TEXT_NODE and "Command" in x.data: |
| 519 commandline = x.data | 523 commandline = x.data |
| 520 break | 524 break |
| 521 | 525 |
| 522 raw_errors = parsed_file.getElementsByTagName("error") | 526 raw_errors = parsed_file.getElementsByTagName("error") |
| 523 for raw_error in raw_errors: | 527 for raw_error in raw_errors: |
| 524 # Ignore "possible" leaks for now by default. | 528 # Ignore "possible" leaks for now by default. |
| 525 if (self._show_all_leaks or | 529 if (self._show_all_leaks or |
| 526 getTextOf(raw_error, "kind") != "Leak_PossiblyLost"): | 530 getTextOf(raw_error, "kind") != "Leak_PossiblyLost"): |
| 527 error = ValgrindError(self._source_dir, raw_error, commandline) | 531 error = ValgrindError(self._source_dir, |
| 532 raw_error, commandline, testcase) |
| 528 if error not in cur_report_errors: | 533 if error not in cur_report_errors: |
| 529 # We haven't seen such errors doing this report yet... | 534 # We haven't seen such errors doing this report yet... |
| 530 if error in self._errors: | 535 if error in self._errors: |
| 531 # ... but we saw it in earlier reports, e.g. previous UI test | 536 # ... but we saw it in earlier reports, e.g. previous UI test |
| 532 cur_report_errors.add("This error was already printed in " | 537 cur_report_errors.add("This error was already printed in " |
| 533 "some other test, see 'hash=#%016X#'" % \ | 538 "some other test, see 'hash=#%016X#'" % \ |
| 534 error.ErrorHash()) | 539 error.ErrorHash()) |
| 535 else: | 540 else: |
| 536 # ... and we haven't seen it in other tests as well | 541 # ... and we haven't seen it in other tests as well |
| 537 self._errors.add(error) | 542 self._errors.add(error) |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 608 parser.add_option("", "--source_dir", | 613 parser.add_option("", "--source_dir", |
| 609 help="path to top of source tree for this build" | 614 help="path to top of source tree for this build" |
| 610 "(used to normalize source paths in baseline)") | 615 "(used to normalize source paths in baseline)") |
| 611 | 616 |
| 612 (options, args) = parser.parse_args() | 617 (options, args) = parser.parse_args() |
| 613 if len(args) == 0: | 618 if len(args) == 0: |
| 614 parser.error("no filename specified") | 619 parser.error("no filename specified") |
| 615 filenames = args | 620 filenames = args |
| 616 | 621 |
| 617 analyzer = MemcheckAnalyzer(options.source_dir, use_gdb=True) | 622 analyzer = MemcheckAnalyzer(options.source_dir, use_gdb=True) |
| 618 retcode = analyzer.Report(filenames) | 623 retcode = analyzer.Report(filenames, None) |
| 619 | 624 |
| 620 sys.exit(retcode) | 625 sys.exit(retcode) |
| 621 | 626 |
| 622 if __name__ == "__main__": | 627 if __name__ == "__main__": |
| 623 _main() | 628 _main() |
| OLD | NEW |