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 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 156