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 |