1# -*- coding: utf-8 -*-
2# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3# See https://llvm.org/LICENSE.txt for license information.
4# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5""" This module is responsible for the Clang executable.
6
7Since Clang command line interface is so rich, but this project is using only
8a subset of that, it makes sense to create a function specific wrapper. """
9
10import subprocess
11import re
12from libscanbuild import run_command
13from libscanbuild.shell import decode
14
15__all__ = ['get_version', 'get_arguments', 'get_checkers', 'is_ctu_capable',
16           'get_triple_arch']
17
18# regex for activated checker
19ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$')
20
21
22class ClangErrorException(Exception):
23    def __init__(self, error):
24        self.error = error
25
26
27def get_version(clang):
28    """ Returns the compiler version as string.
29
30    :param clang:   the compiler we are using
31    :return:        the version string printed to stderr """
32
33    output = run_command([clang, '-v'])
34    # the relevant version info is in the first line
35    return output[0]
36
37
38def get_arguments(command, cwd):
39    """ Capture Clang invocation.
40
41    :param command: the compilation command
42    :param cwd:     the current working directory
43    :return:        the detailed front-end invocation command """
44
45    cmd = command[:]
46    cmd.insert(1, '-###')
47    cmd.append('-fno-color-diagnostics')
48
49    output = run_command(cmd, cwd=cwd)
50    # The relevant information is in the last line of the output.
51    # Don't check if finding last line fails, would throw exception anyway.
52    last_line = output[-1]
53    if re.search(r'clang(.*): error:', last_line):
54        raise ClangErrorException(last_line)
55    return decode(last_line)
56
57
58def get_active_checkers(clang, plugins):
59    """ Get the active checker list.
60
61    :param clang:   the compiler we are using
62    :param plugins: list of plugins which was requested by the user
63    :return:        list of checker names which are active
64
65    To get the default checkers we execute Clang to print how this
66    compilation would be called. And take out the enabled checker from the
67    arguments. For input file we specify stdin and pass only language
68    information. """
69
70    def get_active_checkers_for(language):
71        """ Returns a list of active checkers for the given language. """
72
73        load_args = [arg
74                     for plugin in plugins
75                     for arg in ['-Xclang', '-load', '-Xclang', plugin]]
76        cmd = [clang, '--analyze'] + load_args + ['-x', language, '-']
77        return [ACTIVE_CHECKER_PATTERN.match(arg).group(1)
78                for arg in get_arguments(cmd, '.')
79                if ACTIVE_CHECKER_PATTERN.match(arg)]
80
81    result = set()
82    for language in ['c', 'c++', 'objective-c', 'objective-c++']:
83        result.update(get_active_checkers_for(language))
84    return frozenset(result)
85
86
87def is_active(checkers):
88    """ Returns a method, which classifies the checker active or not,
89    based on the received checker name list. """
90
91    def predicate(checker):
92        """ Returns True if the given checker is active. """
93
94        return any(pattern.match(checker) for pattern in predicate.patterns)
95
96    predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers]
97    return predicate
98
99
100def parse_checkers(stream):
101    """ Parse clang -analyzer-checker-help output.
102
103    Below the line 'CHECKERS:' are there the name description pairs.
104    Many of them are in one line, but some long named checker has the
105    name and the description in separate lines.
106
107    The checker name is always prefixed with two space character. The
108    name contains no whitespaces. Then followed by newline (if it's
109    too long) or other space characters comes the description of the
110    checker. The description ends with a newline character.
111
112    :param stream:  list of lines to parse
113    :return:        generator of tuples
114
115    (<checker name>, <checker description>) """
116
117    lines = iter(stream)
118    # find checkers header
119    for line in lines:
120        if re.match(r'^CHECKERS:', line):
121            break
122    # find entries
123    state = None
124    for line in lines:
125        if state and not re.match(r'^\s\s\S', line):
126            yield (state, line.strip())
127            state = None
128        elif re.match(r'^\s\s\S+$', line.rstrip()):
129            state = line.strip()
130        else:
131            pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)')
132            match = pattern.match(line.rstrip())
133            if match:
134                current = match.groupdict()
135                yield (current['key'], current['value'])
136
137
138def get_checkers(clang, plugins):
139    """ Get all the available checkers from default and from the plugins.
140
141    :param clang:   the compiler we are using
142    :param plugins: list of plugins which was requested by the user
143    :return:        a dictionary of all available checkers and its status
144
145    {<checker name>: (<checker description>, <is active by default>)} """
146
147    load = [elem for plugin in plugins for elem in ['-load', plugin]]
148    cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help']
149
150    lines = run_command(cmd)
151
152    is_active_checker = is_active(get_active_checkers(clang, plugins))
153
154    checkers = {
155        name: (description, is_active_checker(name))
156        for name, description in parse_checkers(lines)
157    }
158    if not checkers:
159        raise Exception('Could not query Clang for available checkers.')
160
161    return checkers
162
163
164def is_ctu_capable(extdef_map_cmd):
165    """ Detects if the current (or given) clang and external definition mapping
166    executables are CTU compatible. """
167
168    try:
169        run_command([extdef_map_cmd, '-version'])
170    except (OSError, subprocess.CalledProcessError):
171        return False
172    return True
173
174
175def get_triple_arch(command, cwd):
176    """Returns the architecture part of the target triple for the given
177    compilation command. """
178
179    cmd = get_arguments(command, cwd)
180    try:
181        separator = cmd.index("-triple")
182        return cmd[separator + 1]
183    except (IndexError, ValueError):
184        return ""
185