OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright (c) 2006-2008 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_analyze.py |
| 7 |
| 8 ''' Given a valgrind XML file, parses errors and uniques them.''' |
| 9 |
| 10 import logging |
| 11 import optparse |
| 12 import os |
| 13 import sys |
| 14 from xml.dom.minidom import parse |
| 15 |
| 16 # These are functions (using C++ mangled names) that we look for in stack |
| 17 # traces. We don't show stack frames while pretty printing when they are below |
| 18 # any of the following: |
| 19 _TOP_OF_STACK_POINTS = [ |
| 20 # Don't show our testing framework. |
| 21 "testing::Test::Run()", |
| 22 # Also don't show the internals of libc/pthread. |
| 23 "start_thread" |
| 24 ] |
| 25 |
| 26 def getTextOf(top_node, name): |
| 27 ''' Returns all text in all DOM nodes with a certain |name| that are children |
| 28 of |top_node|. |
| 29 ''' |
| 30 |
| 31 text = "" |
| 32 for nodes_named in top_node.getElementsByTagName(name): |
| 33 text += "".join([node.data for node in nodes_named.childNodes |
| 34 if node.nodeType == node.TEXT_NODE]) |
| 35 return text |
| 36 |
| 37 def removeCommonRoot(source_dir, directory): |
| 38 '''Returns a string with the string prefix |source_dir| removed from |
| 39 |directory|.''' |
| 40 if source_dir: |
| 41 # Do this for safety, just in case directory is an absolute path outside of |
| 42 # source_dir. |
| 43 prefix = os.path.commonprefix([source_dir, directory]) |
| 44 return directory[len(prefix) + 1:] |
| 45 |
| 46 return directory |
| 47 |
| 48 # Constants that give real names to the abbreviations in valgrind XML output. |
| 49 INSTRUCTION_POINTER = "ip" |
| 50 OBJECT_FILE = "obj" |
| 51 FUNCTION_NAME = "fn" |
| 52 SRC_FILE_DIR = "dir" |
| 53 SRC_FILE_NAME = "file" |
| 54 SRC_LINE = "line" |
| 55 |
| 56 class ValgrindError: |
| 57 ''' Takes a <DOM Element: error> node and reads all the data from it. A |
| 58 ValgrindError is immutable and is hashed on its pretty printed output. |
| 59 ''' |
| 60 |
| 61 def __init__(self, source_dir, error_node): |
| 62 ''' Copies all the relevant information out of the DOM and into object |
| 63 properties. |
| 64 |
| 65 Args: |
| 66 error_node: The <error></error> DOM node we're extracting from. |
| 67 source_dir: Prefix that should be stripped from the <dir> node. |
| 68 ''' |
| 69 |
| 70 self._kind = getTextOf(error_node, "kind") |
| 71 self._what = getTextOf(error_node, "what") |
| 72 |
| 73 self._frames = [] |
| 74 stack_node = error_node.getElementsByTagName("stack")[0] |
| 75 |
| 76 for frame in stack_node.getElementsByTagName("frame"): |
| 77 frame_dict = { |
| 78 INSTRUCTION_POINTER : getTextOf(frame, INSTRUCTION_POINTER), |
| 79 OBJECT_FILE : getTextOf(frame, OBJECT_FILE), |
| 80 FUNCTION_NAME : getTextOf(frame, FUNCTION_NAME), |
| 81 SRC_FILE_DIR : removeCommonRoot( |
| 82 source_dir, getTextOf(frame, SRC_FILE_DIR)), |
| 83 SRC_FILE_NAME : getTextOf(frame, SRC_FILE_NAME), |
| 84 SRC_LINE : getTextOf(frame, SRC_LINE) |
| 85 } |
| 86 |
| 87 self._frames += [frame_dict] |
| 88 |
| 89 if frame_dict[FUNCTION_NAME] in _TOP_OF_STACK_POINTS: |
| 90 break |
| 91 |
| 92 def __str__(self): |
| 93 ''' Pretty print the type and stack frame of this specific error.''' |
| 94 output = self._kind + "\n" |
| 95 for frame in self._frames: |
| 96 output += (" " + (frame[FUNCTION_NAME] or frame[INSTRUCTION_POINTER]) + |
| 97 " (") |
| 98 |
| 99 if frame[SRC_FILE_DIR] != "": |
| 100 output += (frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_DIR] + ":" + |
| 101 frame[SRC_LINE]) |
| 102 else: |
| 103 output += frame[OBJECT_FILE] |
| 104 output += ")\n" |
| 105 |
| 106 return output |
| 107 |
| 108 def UniqueString(self): |
| 109 ''' String to use for object identity. Don't print this, use str(obj) |
| 110 instead.''' |
| 111 rep = self._kind + " " |
| 112 for frame in self._frames: |
| 113 rep += frame[FUNCTION_NAME] |
| 114 |
| 115 if frame[SRC_FILE_DIR] != "": |
| 116 rep += frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_NAME] |
| 117 else: |
| 118 rep += frame[OBJECT_FILE] |
| 119 |
| 120 return rep |
| 121 |
| 122 def __hash__(self): |
| 123 return hash(self.UniqueString()) |
| 124 def __eq__(self, rhs): |
| 125 return self.UniqueString() == rhs |
| 126 |
| 127 class ValgrindAnalyze: |
| 128 ''' Given a set of Valgrind XML files, parse all the errors out of them, |
| 129 unique them and output the results.''' |
| 130 |
| 131 def __init__(self, source_dir, files): |
| 132 '''Reads in a set of files. |
| 133 |
| 134 Args: |
| 135 source_dir: Path to top of source tree for this build |
| 136 files: A list of filenames. |
| 137 ''' |
| 138 |
| 139 self._errors = set() |
| 140 for file in files: |
| 141 raw_errors = parse(file).getElementsByTagName("error") |
| 142 for raw_error in raw_errors: |
| 143 self._errors.add(ValgrindError(source_dir, raw_error)) |
| 144 |
| 145 def Report(self): |
| 146 if self._errors: |
| 147 logging.error("FAIL! There were %s errors: " % len(self._errors)) |
| 148 |
| 149 for error in self._errors: |
| 150 logging.error(error) |
| 151 |
| 152 return -1 |
| 153 |
| 154 logging.info("PASS! No errors found!") |
| 155 return 0 |
| 156 |
| 157 def _main(): |
| 158 '''For testing only. The ValgrindAnalyze class should be imported instead.''' |
| 159 retcode = 0 |
| 160 parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") |
| 161 parser.add_option("", "--source_dir", |
| 162 help="path to top of source tree for this build" |
| 163 "(used to normalize source paths in baseline)") |
| 164 |
| 165 (options, args) = parser.parse_args() |
| 166 if not len(args) >= 1: |
| 167 parser.error("no filename specified") |
| 168 filenames = args |
| 169 |
| 170 analyzer = ValgrindAnalyze(options.source_dir, filenames) |
| 171 retcode = analyzer.Report() |
| 172 |
| 173 sys.exit(retcode) |
| 174 |
| 175 if __name__ == "__main__": |
| 176 _main() |
OLD | NEW |