Ben Murdoch | 109988c | 2016-05-18 11:27:45 +0100 | [diff] [blame] | 1 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import logging |
| 6 | import os |
| 7 | import xml.dom.minidom |
| 8 | |
| 9 | from devil.utils import cmd_helper |
| 10 | from pylib import constants |
| 11 | from pylib.constants import host_paths |
| 12 | |
| 13 | |
| 14 | _FINDBUGS_HOME = os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party', |
| 15 | 'findbugs') |
| 16 | _FINDBUGS_JAR = os.path.join(_FINDBUGS_HOME, 'lib', 'findbugs.jar') |
| 17 | _FINDBUGS_MAX_HEAP = 768 |
| 18 | _FINDBUGS_PLUGIN_PATH = os.path.join( |
| 19 | host_paths.DIR_SOURCE_ROOT, 'tools', 'android', 'findbugs_plugin', 'lib', |
| 20 | 'chromiumPlugin.jar') |
| 21 | |
| 22 | |
| 23 | def _ParseXmlResults(results_doc): |
| 24 | warnings = set() |
| 25 | for en in (n for n in results_doc.documentElement.childNodes |
| 26 | if n.nodeType == xml.dom.Node.ELEMENT_NODE): |
| 27 | if en.tagName == 'BugInstance': |
| 28 | warnings.add(_ParseBugInstance(en)) |
| 29 | return warnings |
| 30 | |
| 31 | |
| 32 | def _GetMessage(node): |
| 33 | for c in (n for n in node.childNodes |
| 34 | if n.nodeType == xml.dom.Node.ELEMENT_NODE): |
| 35 | if c.tagName == 'Message': |
| 36 | if (len(c.childNodes) == 1 |
| 37 | and c.childNodes[0].nodeType == xml.dom.Node.TEXT_NODE): |
| 38 | return c.childNodes[0].data |
| 39 | return None |
| 40 | |
| 41 | |
| 42 | def _ParseBugInstance(node): |
| 43 | bug = FindBugsWarning(node.getAttribute('type')) |
| 44 | msg_parts = [] |
| 45 | for c in (n for n in node.childNodes |
| 46 | if n.nodeType == xml.dom.Node.ELEMENT_NODE): |
| 47 | if c.tagName == 'Class': |
| 48 | msg_parts.append(_GetMessage(c)) |
| 49 | elif c.tagName == 'Method': |
| 50 | msg_parts.append(_GetMessage(c)) |
| 51 | elif c.tagName == 'Field': |
| 52 | msg_parts.append(_GetMessage(c)) |
| 53 | elif c.tagName == 'SourceLine': |
| 54 | bug.file_name = c.getAttribute('sourcefile') |
| 55 | if c.hasAttribute('start'): |
| 56 | bug.start_line = int(c.getAttribute('start')) |
| 57 | if c.hasAttribute('end'): |
| 58 | bug.end_line = int(c.getAttribute('end')) |
| 59 | msg_parts.append(_GetMessage(c)) |
| 60 | elif (c.tagName == 'ShortMessage' and len(c.childNodes) == 1 |
| 61 | and c.childNodes[0].nodeType == xml.dom.Node.TEXT_NODE): |
| 62 | msg_parts.append(c.childNodes[0].data) |
| 63 | bug.message = tuple(m for m in msg_parts if m) |
| 64 | return bug |
| 65 | |
| 66 | |
| 67 | class FindBugsWarning(object): |
| 68 | |
| 69 | def __init__(self, bug_type='', end_line=0, file_name='', message=None, |
| 70 | start_line=0): |
| 71 | self.bug_type = bug_type |
| 72 | self.end_line = end_line |
| 73 | self.file_name = file_name |
| 74 | if message is None: |
| 75 | self.message = tuple() |
| 76 | else: |
| 77 | self.message = message |
| 78 | self.start_line = start_line |
| 79 | |
| 80 | def __cmp__(self, other): |
| 81 | return (cmp(self.file_name, other.file_name) |
| 82 | or cmp(self.start_line, other.start_line) |
| 83 | or cmp(self.end_line, other.end_line) |
| 84 | or cmp(self.bug_type, other.bug_type) |
| 85 | or cmp(self.message, other.message)) |
| 86 | |
| 87 | def __eq__(self, other): |
| 88 | return self.__dict__ == other.__dict__ |
| 89 | |
| 90 | def __hash__(self): |
| 91 | return hash((self.bug_type, self.end_line, self.file_name, self.message, |
| 92 | self.start_line)) |
| 93 | |
| 94 | def __ne__(self, other): |
| 95 | return not self == other |
| 96 | |
| 97 | def __str__(self): |
| 98 | return '%s: %s' % (self.bug_type, '\n '.join(self.message)) |
| 99 | |
| 100 | |
| 101 | def Run(exclude, classes_to_analyze, auxiliary_classes, output_file, |
| 102 | findbug_args, jars): |
| 103 | """Run FindBugs. |
| 104 | |
| 105 | Args: |
| 106 | exclude: the exclude xml file, refer to FindBugs's -exclude command option. |
| 107 | classes_to_analyze: the list of classes need to analyze, refer to FindBug's |
| 108 | -onlyAnalyze command line option. |
| 109 | auxiliary_classes: the classes help to analyze, refer to FindBug's |
| 110 | -auxclasspath command line option. |
| 111 | output_file: An optional path to dump XML results to. |
| 112 | findbug_args: A list of addtional command line options to pass to Findbugs. |
| 113 | """ |
| 114 | # TODO(jbudorick): Get this from the build system. |
| 115 | system_classes = [ |
| 116 | os.path.join(constants.ANDROID_SDK_ROOT, 'platforms', |
| 117 | 'android-%s' % constants.ANDROID_SDK_VERSION, 'android.jar') |
| 118 | ] |
| 119 | system_classes.extend(os.path.abspath(classes) |
| 120 | for classes in auxiliary_classes or []) |
| 121 | |
| 122 | cmd = ['java', |
| 123 | '-classpath', '%s:' % _FINDBUGS_JAR, |
| 124 | '-Xmx%dm' % _FINDBUGS_MAX_HEAP, |
| 125 | '-Dfindbugs.home="%s"' % _FINDBUGS_HOME, |
| 126 | '-jar', _FINDBUGS_JAR, |
| 127 | '-textui', '-sortByClass', |
| 128 | '-pluginList', _FINDBUGS_PLUGIN_PATH, '-xml:withMessages'] |
| 129 | if system_classes: |
| 130 | cmd.extend(['-auxclasspath', ':'.join(system_classes)]) |
| 131 | if classes_to_analyze: |
| 132 | cmd.extend(['-onlyAnalyze', classes_to_analyze]) |
| 133 | if exclude: |
| 134 | cmd.extend(['-exclude', os.path.abspath(exclude)]) |
| 135 | if output_file: |
| 136 | cmd.extend(['-output', output_file]) |
| 137 | if findbug_args: |
| 138 | cmd.extend(findbug_args) |
| 139 | cmd.extend(os.path.abspath(j) for j in jars or []) |
| 140 | |
| 141 | if output_file: |
| 142 | _, _, stderr = cmd_helper.GetCmdStatusOutputAndError(cmd) |
| 143 | |
| 144 | results_doc = xml.dom.minidom.parse(output_file) |
| 145 | else: |
| 146 | _, raw_out, stderr = cmd_helper.GetCmdStatusOutputAndError(cmd) |
| 147 | results_doc = xml.dom.minidom.parseString(raw_out) |
| 148 | |
| 149 | for line in stderr.splitlines(): |
| 150 | logging.debug(' %s', line) |
| 151 | |
| 152 | current_warnings_set = _ParseXmlResults(results_doc) |
| 153 | |
| 154 | return (' '.join(cmd), current_warnings_set) |
| 155 | |