blob: 833e77d28bbe10e025445a41f13e0c71c68c7e88 [file] [log] [blame]
Laszlo Nagybc687582016-01-12 22:38:41 +00001# -*- 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
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000018# regex for activated checker
19ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$')
Laszlo Nagybc687582016-01-12 22:38:41 +000020
Laszlo Nagybc687582016-01-12 22:38:41 +000021
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000022def get_version(clang):
23 """ Returns the compiler version as string.
24
25 :param clang: the compiler we are using
26 :return: the version string printed to stderr """
27
28 output = subprocess.check_output([clang, '-v'], stderr=subprocess.STDOUT)
29 return output.decode('utf-8').splitlines()[0]
Laszlo Nagybc687582016-01-12 22:38:41 +000030
31
32def get_arguments(command, cwd):
33 """ Capture Clang invocation.
34
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000035 :param command: the compilation command
36 :param cwd: the current working directory
37 :return: the detailed front-end invocation command """
Laszlo Nagybc687582016-01-12 22:38:41 +000038
39 cmd = command[:]
40 cmd.insert(1, '-###')
41 logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000042
43 output = subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT)
44 # The relevant information is in the last line of the output.
45 # Don't check if finding last line fails, would throw exception anyway.
46 last_line = output.decode('utf-8').splitlines()[-1]
47 if re.search(r'clang(.*): error:', last_line):
48 raise Exception(last_line)
49 return decode(last_line)
Laszlo Nagybc687582016-01-12 22:38:41 +000050
51
52def get_active_checkers(clang, plugins):
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000053 """ Get the active checker list.
Laszlo Nagybc687582016-01-12 22:38:41 +000054
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000055 :param clang: the compiler we are using
56 :param plugins: list of plugins which was requested by the user
57 :return: list of checker names which are active
Laszlo Nagybc687582016-01-12 22:38:41 +000058
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000059 To get the default checkers we execute Clang to print how this
60 compilation would be called. And take out the enabled checker from the
61 arguments. For input file we specify stdin and pass only language
62 information. """
63
64 def get_active_checkers_for(language):
Laszlo Nagybc687582016-01-12 22:38:41 +000065 """ Returns a list of active checkers for the given language. """
66
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000067 load_args = [arg
68 for plugin in plugins
69 for arg in ['-Xclang', '-load', '-Xclang', plugin]]
70 cmd = [clang, '--analyze'] + load_args + ['-x', language, '-']
71 return [ACTIVE_CHECKER_PATTERN.match(arg).group(1)
72 for arg in get_arguments(cmd, '.')
73 if ACTIVE_CHECKER_PATTERN.match(arg)]
Laszlo Nagybc687582016-01-12 22:38:41 +000074
75 result = set()
76 for language in ['c', 'c++', 'objective-c', 'objective-c++']:
Laszlo Nagy4f6a1752016-09-24 00:20:59 +000077 result.update(get_active_checkers_for(language))
78 return frozenset(result)
79
80
81def is_active(checkers):
82 """ Returns a method, which classifies the checker active or not,
83 based on the received checker name list. """
84
85 def predicate(checker):
86 """ Returns True if the given checker is active. """
87
88 return any(pattern.match(checker) for pattern in predicate.patterns)
89
90 predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers]
91 return predicate
92
93
94def 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 checker has the
99 name and the description in separate lines.
100
101 The checker 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 checker. The description ends with a newline character.
105
106 :param stream: list of lines to parse
107 :return: generator of tuples
108
109 (<checker name>, <checker description>) """
110
111 lines = iter(stream)
112 # find checkers header
113 for line in lines:
114 if re.match(r'^CHECKERS:', line):
115 break
116 # find entries
117 state = None
118 for line in lines:
119 if state and not re.match(r'^\s\s\S', line):
120 yield (state, line.strip())
121 state = None
122 elif re.match(r'^\s\s\S+$', line.rstrip()):
123 state = line.strip()
124 else:
125 pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)')
126 match = pattern.match(line.rstrip())
127 if match:
128 current = match.groupdict()
129 yield (current['key'], current['value'])
Laszlo Nagybc687582016-01-12 22:38:41 +0000130
131
132def get_checkers(clang, plugins):
133 """ Get all the available checkers from default and from the plugins.
134
Laszlo Nagy4f6a1752016-09-24 00:20:59 +0000135 :param clang: the compiler we are using
136 :param plugins: list of plugins which was requested by the user
137 :return: a dictionary of all available checkers and its status
Laszlo Nagybc687582016-01-12 22:38:41 +0000138
Laszlo Nagy4f6a1752016-09-24 00:20:59 +0000139 {<checker name>: (<checker description>, <is active by default>)} """
Laszlo Nagybc687582016-01-12 22:38:41 +0000140
141 load = [elem for plugin in plugins for elem in ['-load', plugin]]
142 cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help']
143
144 logging.debug('exec command: %s', ' '.join(cmd))
Laszlo Nagy4f6a1752016-09-24 00:20:59 +0000145 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
146 lines = output.decode('utf-8').splitlines()
147
148 is_active_checker = is_active(get_active_checkers(clang, plugins))
149
Laszlo Nagybc687582016-01-12 22:38:41 +0000150 checkers = {
Laszlo Nagy4f6a1752016-09-24 00:20:59 +0000151 name: (description, is_active_checker(name))
152 for name, description in parse_checkers(lines)
Laszlo Nagybc687582016-01-12 22:38:41 +0000153 }
Laszlo Nagy4f6a1752016-09-24 00:20:59 +0000154 if not checkers:
Laszlo Nagybc687582016-01-12 22:38:41 +0000155 raise Exception('Could not query Clang for available checkers.')
Laszlo Nagy4f6a1752016-09-24 00:20:59 +0000156
157 return checkers