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