Home | History | Annotate | Download | only in utils
      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 
    156