157 lines
5.4 KiB
Python
157 lines
5.4 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# The LLVM Compiler Infrastructure
|
||
|
#
|
||
|
# This file is distributed under the University of Illinois Open Source
|
||
|
# License. See LICENSE.TXT for details.
|
||
|
""" This module is responsible for the Clang executable.
|
||
|
|
||
|
Since Clang command line interface is so rich, but this project is using only
|
||
|
a subset of that, it makes sense to create a function specific wrapper. """
|
||
|
|
||
|
import re
|
||
|
import subprocess
|
||
|
import logging
|
||
|
from libscanbuild.shell import decode
|
||
|
|
||
|
__all__ = ['get_version', 'get_arguments', 'get_checkers']
|
||
|
|
||
|
|
||
|
def get_version(cmd):
|
||
|
""" Returns the compiler version as string. """
|
||
|
|
||
|
lines = subprocess.check_output([cmd, '-v'], stderr=subprocess.STDOUT)
|
||
|
return lines.decode('ascii').splitlines()[0]
|
||
|
|
||
|
|
||
|
def get_arguments(command, cwd):
|
||
|
""" Capture Clang invocation.
|
||
|
|
||
|
This method returns the front-end invocation that would be executed as
|
||
|
a result of the given driver invocation. """
|
||
|
|
||
|
def lastline(stream):
|
||
|
last = None
|
||
|
for line in stream:
|
||
|
last = line
|
||
|
if last is None:
|
||
|
raise Exception("output not found")
|
||
|
return last
|
||
|
|
||
|
cmd = command[:]
|
||
|
cmd.insert(1, '-###')
|
||
|
logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
|
||
|
child = subprocess.Popen(cmd,
|
||
|
cwd=cwd,
|
||
|
universal_newlines=True,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.STDOUT)
|
||
|
line = lastline(child.stdout)
|
||
|
child.stdout.close()
|
||
|
child.wait()
|
||
|
if child.returncode == 0:
|
||
|
if re.search(r'clang(.*): error:', line):
|
||
|
raise Exception(line)
|
||
|
return decode(line)
|
||
|
else:
|
||
|
raise Exception(line)
|
||
|
|
||
|
|
||
|
def get_active_checkers(clang, plugins):
|
||
|
""" To get the default plugins we execute Clang to print how this
|
||
|
compilation would be called.
|
||
|
|
||
|
For input file we specify stdin and pass only language information. """
|
||
|
|
||
|
def checkers(language):
|
||
|
""" Returns a list of active checkers for the given language. """
|
||
|
|
||
|
load = [elem
|
||
|
for plugin in plugins
|
||
|
for elem in ['-Xclang', '-load', '-Xclang', plugin]]
|
||
|
cmd = [clang, '--analyze'] + load + ['-x', language, '-']
|
||
|
pattern = re.compile(r'^-analyzer-checker=(.*)$')
|
||
|
return [pattern.match(arg).group(1)
|
||
|
for arg in get_arguments(cmd, '.') if pattern.match(arg)]
|
||
|
|
||
|
result = set()
|
||
|
for language in ['c', 'c++', 'objective-c', 'objective-c++']:
|
||
|
result.update(checkers(language))
|
||
|
return result
|
||
|
|
||
|
|
||
|
def get_checkers(clang, plugins):
|
||
|
""" Get all the available checkers from default and from the plugins.
|
||
|
|
||
|
clang -- the compiler we are using
|
||
|
plugins -- list of plugins which was requested by the user
|
||
|
|
||
|
This method returns a dictionary of all available checkers and status.
|
||
|
|
||
|
{<plugin name>: (<plugin description>, <is active by default>)} """
|
||
|
|
||
|
plugins = plugins if plugins else []
|
||
|
|
||
|
def parse_checkers(stream):
|
||
|
""" Parse clang -analyzer-checker-help output.
|
||
|
|
||
|
Below the line 'CHECKERS:' are there the name description pairs.
|
||
|
Many of them are in one line, but some long named plugins has the
|
||
|
name and the description in separate lines.
|
||
|
|
||
|
The plugin name is always prefixed with two space character. The
|
||
|
name contains no whitespaces. Then followed by newline (if it's
|
||
|
too long) or other space characters comes the description of the
|
||
|
plugin. The description ends with a newline character. """
|
||
|
|
||
|
# find checkers header
|
||
|
for line in stream:
|
||
|
if re.match(r'^CHECKERS:', line):
|
||
|
break
|
||
|
# find entries
|
||
|
state = None
|
||
|
for line in stream:
|
||
|
if state and not re.match(r'^\s\s\S', line):
|
||
|
yield (state, line.strip())
|
||
|
state = None
|
||
|
elif re.match(r'^\s\s\S+$', line.rstrip()):
|
||
|
state = line.strip()
|
||
|
else:
|
||
|
pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)')
|
||
|
match = pattern.match(line.rstrip())
|
||
|
if match:
|
||
|
current = match.groupdict()
|
||
|
yield (current['key'], current['value'])
|
||
|
|
||
|
def is_active(actives, entry):
|
||
|
""" Returns true if plugin name is matching the active plugin names.
|
||
|
|
||
|
actives -- set of active plugin names (or prefixes).
|
||
|
entry -- the current plugin name to judge.
|
||
|
|
||
|
The active plugin names are specific plugin names or prefix of some
|
||
|
names. One example for prefix, when it say 'unix' and it shall match
|
||
|
on 'unix.API', 'unix.Malloc' and 'unix.MallocSizeof'. """
|
||
|
|
||
|
return any(re.match(r'^' + a + r'(\.|$)', entry) for a in actives)
|
||
|
|
||
|
actives = get_active_checkers(clang, plugins)
|
||
|
|
||
|
load = [elem for plugin in plugins for elem in ['-load', plugin]]
|
||
|
cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help']
|
||
|
|
||
|
logging.debug('exec command: %s', ' '.join(cmd))
|
||
|
child = subprocess.Popen(cmd,
|
||
|
universal_newlines=True,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.STDOUT)
|
||
|
checkers = {
|
||
|
k: (v, is_active(actives, k))
|
||
|
for k, v in parse_checkers(child.stdout)
|
||
|
}
|
||
|
child.stdout.close()
|
||
|
child.wait()
|
||
|
if child.returncode == 0 and len(checkers):
|
||
|
return checkers
|
||
|
else:
|
||
|
raise Exception('Could not query Clang for available checkers.')
|