You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
5.4 KiB
157 lines
5.4 KiB
# -*- 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.')
|