• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8Since Clang command line interface is so rich, but this project is using only
9a subset of that, it makes sense to create a function specific wrapper. """
10
11import re
12import subprocess
13import logging
14from libscanbuild.shell import decode
15
16__all__ = ['get_version', 'get_arguments', 'get_checkers']
17
18
19def 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
26def 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
59def 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
82def 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