blob: 04568938ee32590482ab9588e9ade78fe3c8f7d6 [file] [log] [blame]
Ben Murdoch109988c2016-05-18 11:27:45 +01001# 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
5import logging
6import os
7import xml.dom.minidom
8
9from devil.utils import cmd_helper
10from pylib import constants
11from 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
23def _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
32def _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
42def _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
67class 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
101def 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