diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2017-01-02 19:18:08 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2017-01-02 19:18:08 +0000 |
commit | bab175ec4b075c8076ba14c762900392533f6ee4 (patch) | |
tree | 01f4f29419a2cb10abe13c1e63cd2a66068b0137 /tools/scan-build-py/libscanbuild/clang.py | |
parent | 8b7a8012d223fac5d17d16a66bb39168a9a1dfc0 (diff) |
Notes
Diffstat (limited to 'tools/scan-build-py/libscanbuild/clang.py')
-rw-r--r-- | tools/scan-build-py/libscanbuild/clang.py | 191 |
1 files changed, 96 insertions, 95 deletions
diff --git a/tools/scan-build-py/libscanbuild/clang.py b/tools/scan-build-py/libscanbuild/clang.py index 0c3454b16a76b..833e77d28bbe1 100644 --- a/tools/scan-build-py/libscanbuild/clang.py +++ b/tools/scan-build-py/libscanbuild/clang.py @@ -15,142 +15,143 @@ from libscanbuild.shell import decode __all__ = ['get_version', 'get_arguments', 'get_checkers'] +# regex for activated checker +ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$') -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_version(clang): + """ Returns the compiler version as string. + + :param clang: the compiler we are using + :return: the version string printed to stderr """ + + output = subprocess.check_output([clang, '-v'], stderr=subprocess.STDOUT) + return output.decode('utf-8').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 + :param command: the compilation command + :param cwd: the current working directory + :return: the detailed front-end invocation command """ 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) + + output = subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT) + # The relevant information is in the last line of the output. + # Don't check if finding last line fails, would throw exception anyway. + last_line = output.decode('utf-8').splitlines()[-1] + if re.search(r'clang(.*): error:', last_line): + raise Exception(last_line) + return decode(last_line) def get_active_checkers(clang, plugins): - """ To get the default plugins we execute Clang to print how this - compilation would be called. + """ Get the active checker list. - For input file we specify stdin and pass only language information. """ + :param clang: the compiler we are using + :param plugins: list of plugins which was requested by the user + :return: list of checker names which are active - def checkers(language): + To get the default checkers we execute Clang to print how this + compilation would be called. And take out the enabled checker from the + arguments. For input file we specify stdin and pass only language + information. """ + + def get_active_checkers_for(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)] + load_args = [arg + for plugin in plugins + for arg in ['-Xclang', '-load', '-Xclang', plugin]] + cmd = [clang, '--analyze'] + load_args + ['-x', language, '-'] + return [ACTIVE_CHECKER_PATTERN.match(arg).group(1) + for arg in get_arguments(cmd, '.') + if ACTIVE_CHECKER_PATTERN.match(arg)] result = set() for language in ['c', 'c++', 'objective-c', 'objective-c++']: - result.update(checkers(language)) - return result + result.update(get_active_checkers_for(language)) + return frozenset(result) -def get_checkers(clang, plugins): - """ Get all the available checkers from default and from the plugins. +def is_active(checkers): + """ Returns a method, which classifies the checker active or not, + based on the received checker name list. """ - clang -- the compiler we are using - plugins -- list of plugins which was requested by the user + def predicate(checker): + """ Returns True if the given checker is active. """ - This method returns a dictionary of all available checkers and status. + return any(pattern.match(checker) for pattern in predicate.patterns) - {<plugin name>: (<plugin description>, <is active by default>)} """ + predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers] + return predicate - plugins = plugins if plugins else [] - def parse_checkers(stream): - """ Parse clang -analyzer-checker-help output. +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. + Below the line 'CHECKERS:' are there the name description pairs. + Many of them are in one line, but some long named checker 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. """ + The checker 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 + checker. 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']) + :param stream: list of lines to parse + :return: generator of tuples - def is_active(actives, entry): - """ Returns true if plugin name is matching the active plugin names. + (<checker name>, <checker description>) """ - actives -- set of active plugin names (or prefixes). - entry -- the current plugin name to judge. + lines = iter(stream) + # find checkers header + for line in lines: + if re.match(r'^CHECKERS:', line): + break + # find entries + state = None + for line in lines: + 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']) - 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) +def get_checkers(clang, plugins): + """ Get all the available checkers from default and from the plugins. + + :param clang: the compiler we are using + :param plugins: list of plugins which was requested by the user + :return: a dictionary of all available checkers and its status - actives = get_active_checkers(clang, plugins) + {<checker name>: (<checker description>, <is active by default>)} """ 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) + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + lines = output.decode('utf-8').splitlines() + + is_active_checker = is_active(get_active_checkers(clang, plugins)) + checkers = { - k: (v, is_active(actives, k)) - for k, v in parse_checkers(child.stdout) + name: (description, is_active_checker(name)) + for name, description in parse_checkers(lines) } - child.stdout.close() - child.wait() - if child.returncode == 0 and len(checkers): - return checkers - else: + if not checkers: raise Exception('Could not query Clang for available checkers.') + + return checkers |