Home | History | Annotate | Download | only in libscanbuild
      1 # -*- coding: utf-8 -*-
      2 #                     The LLVM Compiler Infrastructure
      3 #
      4 # This file is distributed under the University of Illinois Open Source
      5 # License. See LICENSE.TXT for details.
      6 """ This module is responsible for the Clang executable.
      7 
      8 Since Clang command line interface is so rich, but this project is using only
      9 a subset of that, it makes sense to create a function specific wrapper. """
     10 
     11 import re
     12 import subprocess
     13 import logging
     14 from libscanbuild.shell import decode
     15 
     16 __all__ = ['get_version', 'get_arguments', 'get_checkers']
     17 
     18 
     19 def get_version(cmd):
     20     """ Returns the compiler version as string. """
     21 
     22     lines = subprocess.check_output([cmd, '-v'], stderr=subprocess.STDOUT)
     23     return lines.decode('ascii').splitlines()[0]
     24 
     25 
     26 def get_arguments(command, cwd):
     27     """ Capture Clang invocation.
     28 
     29     This method returns the front-end invocation that would be executed as
     30     a result of the given driver invocation. """
     31 
     32     def lastline(stream):
     33         last = None
     34         for line in stream:
     35             last = line
     36         if last is None:
     37             raise Exception("output not found")
     38         return last
     39 
     40     cmd = command[:]
     41     cmd.insert(1, '-###')
     42     logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
     43     child = subprocess.Popen(cmd,
     44                              cwd=cwd,
     45                              universal_newlines=True,
     46                              stdout=subprocess.PIPE,
     47                              stderr=subprocess.STDOUT)
     48     line = lastline(child.stdout)
     49     child.stdout.close()
     50     child.wait()
     51     if child.returncode == 0:
     52         if re.search(r'clang(.*): error:', line):
     53             raise Exception(line)
     54         return decode(line)
     55     else:
     56         raise Exception(line)
     57 
     58 
     59 def get_active_checkers(clang, plugins):
     60     """ To get the default plugins we execute Clang to print how this
     61     compilation would be called.
     62 
     63     For input file we specify stdin and pass only language information. """
     64 
     65     def checkers(language):
     66         """ Returns a list of active checkers for the given language. """
     67 
     68         load = [elem
     69                 for plugin in plugins
     70                 for elem in ['-Xclang', '-load', '-Xclang', plugin]]
     71         cmd = [clang, '--analyze'] + load + ['-x', language, '-']
     72         pattern = re.compile(r'^-analyzer-checker=(.*)$')
     73         return [pattern.match(arg).group(1)
     74                 for arg in get_arguments(cmd, '.') if pattern.match(arg)]
     75 
     76     result = set()
     77     for language in ['c', 'c++', 'objective-c', 'objective-c++']:
     78         result.update(checkers(language))
     79     return result
     80 
     81 
     82 def get_checkers(clang, plugins):
     83     """ Get all the available checkers from default and from the plugins.
     84 
     85     clang -- the compiler we are using
     86     plugins -- list of plugins which was requested by the user
     87 
     88     This method returns a dictionary of all available checkers and status.
     89 
     90     {<plugin name>: (<plugin description>, <is active by default>)} """
     91 
     92     plugins = plugins if plugins else []
     93 
     94     def parse_checkers(stream):
     95         """ Parse clang -analyzer-checker-help output.
     96 
     97         Below the line 'CHECKERS:' are there the name description pairs.
     98         Many of them are in one line, but some long named plugins has the
     99         name and the description in separate lines.
    100 
    101         The plugin name is always prefixed with two space character. The
    102         name contains no whitespaces. Then followed by newline (if it's
    103         too long) or other space characters comes the description of the
    104         plugin. The description ends with a newline character. """
    105 
    106         # find checkers header
    107         for line in stream:
    108             if re.match(r'^CHECKERS:', line):
    109                 break
    110         # find entries
    111         state = None
    112         for line in stream:
    113             if state and not re.match(r'^\s\s\S', line):
    114                 yield (state, line.strip())
    115                 state = None
    116             elif re.match(r'^\s\s\S+$', line.rstrip()):
    117                 state = line.strip()
    118             else:
    119                 pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)')
    120                 match = pattern.match(line.rstrip())
    121                 if match:
    122                     current = match.groupdict()
    123                     yield (current['key'], current['value'])
    124 
    125     def is_active(actives, entry):
    126         """ Returns true if plugin name is matching the active plugin names.
    127 
    128         actives -- set of active plugin names (or prefixes).
    129         entry -- the current plugin name to judge.
    130 
    131         The active plugin names are specific plugin names or prefix of some
    132         names. One example for prefix, when it say 'unix' and it shall match
    133         on 'unix.API', 'unix.Malloc' and 'unix.MallocSizeof'. """
    134 
    135         return any(re.match(r'^' + a + r'(\.|$)', entry) for a in actives)
    136 
    137     actives = get_active_checkers(clang, plugins)
    138 
    139     load = [elem for plugin in plugins for elem in ['-load', plugin]]
    140     cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help']
    141 
    142     logging.debug('exec command: %s', ' '.join(cmd))
    143     child = subprocess.Popen(cmd,
    144                              universal_newlines=True,
    145                              stdout=subprocess.PIPE,
    146                              stderr=subprocess.STDOUT)
    147     checkers = {
    148         k: (v, is_active(actives, k))
    149         for k, v in parse_checkers(child.stdout)
    150     }
    151     child.stdout.close()
    152     child.wait()
    153     if child.returncode == 0 and len(checkers):
    154         return checkers
    155     else:
    156         raise Exception('Could not query Clang for available checkers.')
    157