Index: tools/valgrind/valgrind_analyze.py |
diff --git a/tools/valgrind/valgrind_analyze.py b/tools/valgrind/valgrind_analyze.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..c73f6bf05309969cca80275a9ae4b8856c70c79d |
--- /dev/null |
+++ b/tools/valgrind/valgrind_analyze.py |
@@ -0,0 +1,176 @@ |
+#!/usr/bin/python |
+# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+# valgrind_analyze.py |
+ |
+''' Given a valgrind XML file, parses errors and uniques them.''' |
+ |
+import logging |
+import optparse |
+import os |
+import sys |
+from xml.dom.minidom import parse |
+ |
+# These are functions (using C++ mangled names) that we look for in stack |
+# traces. We don't show stack frames while pretty printing when they are below |
+# any of the following: |
+_TOP_OF_STACK_POINTS = [ |
+ # Don't show our testing framework. |
+ "testing::Test::Run()", |
+ # Also don't show the internals of libc/pthread. |
+ "start_thread" |
+] |
+ |
+def getTextOf(top_node, name): |
+ ''' Returns all text in all DOM nodes with a certain |name| that are children |
+ of |top_node|. |
+ ''' |
+ |
+ text = "" |
+ for nodes_named in top_node.getElementsByTagName(name): |
+ text += "".join([node.data for node in nodes_named.childNodes |
+ if node.nodeType == node.TEXT_NODE]) |
+ return text |
+ |
+def removeCommonRoot(source_dir, directory): |
+ '''Returns a string with the string prefix |source_dir| removed from |
+ |directory|.''' |
+ if source_dir: |
+ # Do this for safety, just in case directory is an absolute path outside of |
+ # source_dir. |
+ prefix = os.path.commonprefix([source_dir, directory]) |
+ return directory[len(prefix) + 1:] |
+ |
+ return directory |
+ |
+# Constants that give real names to the abbreviations in valgrind XML output. |
+INSTRUCTION_POINTER = "ip" |
+OBJECT_FILE = "obj" |
+FUNCTION_NAME = "fn" |
+SRC_FILE_DIR = "dir" |
+SRC_FILE_NAME = "file" |
+SRC_LINE = "line" |
+ |
+class ValgrindError: |
+ ''' Takes a <DOM Element: error> node and reads all the data from it. A |
+ ValgrindError is immutable and is hashed on its pretty printed output. |
+ ''' |
+ |
+ def __init__(self, source_dir, error_node): |
+ ''' Copies all the relevant information out of the DOM and into object |
+ properties. |
+ |
+ Args: |
+ error_node: The <error></error> DOM node we're extracting from. |
+ source_dir: Prefix that should be stripped from the <dir> node. |
+ ''' |
+ |
+ self._kind = getTextOf(error_node, "kind") |
+ self._what = getTextOf(error_node, "what") |
+ |
+ self._frames = [] |
+ stack_node = error_node.getElementsByTagName("stack")[0] |
+ |
+ for frame in stack_node.getElementsByTagName("frame"): |
+ frame_dict = { |
+ INSTRUCTION_POINTER : getTextOf(frame, INSTRUCTION_POINTER), |
+ OBJECT_FILE : getTextOf(frame, OBJECT_FILE), |
+ FUNCTION_NAME : getTextOf(frame, FUNCTION_NAME), |
+ SRC_FILE_DIR : removeCommonRoot( |
+ source_dir, getTextOf(frame, SRC_FILE_DIR)), |
+ SRC_FILE_NAME : getTextOf(frame, SRC_FILE_NAME), |
+ SRC_LINE : getTextOf(frame, SRC_LINE) |
+ } |
+ |
+ self._frames += [frame_dict] |
+ |
+ if frame_dict[FUNCTION_NAME] in _TOP_OF_STACK_POINTS: |
+ break |
+ |
+ def __str__(self): |
+ ''' Pretty print the type and stack frame of this specific error.''' |
+ output = self._kind + "\n" |
+ for frame in self._frames: |
+ output += (" " + (frame[FUNCTION_NAME] or frame[INSTRUCTION_POINTER]) + |
+ " (") |
+ |
+ if frame[SRC_FILE_DIR] != "": |
+ output += (frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_DIR] + ":" + |
+ frame[SRC_LINE]) |
+ else: |
+ output += frame[OBJECT_FILE] |
+ output += ")\n" |
+ |
+ return output |
+ |
+ def UniqueString(self): |
+ ''' String to use for object identity. Don't print this, use str(obj) |
+ instead.''' |
+ rep = self._kind + " " |
+ for frame in self._frames: |
+ rep += frame[FUNCTION_NAME] |
+ |
+ if frame[SRC_FILE_DIR] != "": |
+ rep += frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_NAME] |
+ else: |
+ rep += frame[OBJECT_FILE] |
+ |
+ return rep |
+ |
+ def __hash__(self): |
+ return hash(self.UniqueString()) |
+ def __eq__(self, rhs): |
+ return self.UniqueString() == rhs |
+ |
+class ValgrindAnalyze: |
+ ''' Given a set of Valgrind XML files, parse all the errors out of them, |
+ unique them and output the results.''' |
+ |
+ def __init__(self, source_dir, files): |
+ '''Reads in a set of files. |
+ |
+ Args: |
+ source_dir: Path to top of source tree for this build |
+ files: A list of filenames. |
+ ''' |
+ |
+ self._errors = set() |
+ for file in files: |
+ raw_errors = parse(file).getElementsByTagName("error") |
+ for raw_error in raw_errors: |
+ self._errors.add(ValgrindError(source_dir, raw_error)) |
+ |
+ def Report(self): |
+ if self._errors: |
+ logging.error("FAIL! There were %s errors: " % len(self._errors)) |
+ |
+ for error in self._errors: |
+ logging.error(error) |
+ |
+ return -1 |
+ |
+ logging.info("PASS! No errors found!") |
+ return 0 |
+ |
+def _main(): |
+ '''For testing only. The ValgrindAnalyze class should be imported instead.''' |
+ retcode = 0 |
+ parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") |
+ parser.add_option("", "--source_dir", |
+ help="path to top of source tree for this build" |
+ "(used to normalize source paths in baseline)") |
+ |
+ (options, args) = parser.parse_args() |
+ if not len(args) >= 1: |
+ parser.error("no filename specified") |
+ filenames = args |
+ |
+ analyzer = ValgrindAnalyze(options.source_dir, filenames) |
+ retcode = analyzer.Report() |
+ |
+ sys.exit(retcode) |
+ |
+if __name__ == "__main__": |
+ _main() |