diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2017-04-16 16:02:28 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2017-04-16 16:02:28 +0000 |
commit | 7442d6faa2719e4e7d33a7021c406c5a4facd74d (patch) | |
tree | c72b9241553fc9966179aba84f90f17bfa9235c3 /tools/scan-build-py/libscanbuild/analyze.py | |
parent | b52119637f743680a99710ce5fdb6646da2772af (diff) |
Diffstat (limited to 'tools/scan-build-py/libscanbuild/analyze.py')
-rw-r--r-- | tools/scan-build-py/libscanbuild/analyze.py | 766 |
1 files changed, 395 insertions, 371 deletions
diff --git a/tools/scan-build-py/libscanbuild/analyze.py b/tools/scan-build-py/libscanbuild/analyze.py index 244c34b75837d..a09c72389d762 100644 --- a/tools/scan-build-py/libscanbuild/analyze.py +++ b/tools/scan-build-py/libscanbuild/analyze.py @@ -11,72 +11,75 @@ To run the static analyzer against a build is done in multiple steps: -- Analyze: run the analyzer against the captured commands, -- Report: create a cover report from the analyzer outputs. """ -import sys import re import os import os.path import json -import argparse import logging -import subprocess import multiprocessing -from libscanbuild import initialize_logging, tempdir, command_entry_point -from libscanbuild.runner import run +import tempfile +import functools +import subprocess +import contextlib +import datetime + +from libscanbuild import command_entry_point, compiler_wrapper, \ + wrapper_environment, run_build, run_command +from libscanbuild.arguments import parse_args_for_scan_build, \ + parse_args_for_analyze_build from libscanbuild.intercept import capture -from libscanbuild.report import report_directory, document -from libscanbuild.clang import get_checkers -from libscanbuild.compilation import split_command +from libscanbuild.report import document +from libscanbuild.compilation import split_command, classify_source, \ + compiler_language +from libscanbuild.clang import get_version, get_arguments +from libscanbuild.shell import decode -__all__ = ['analyze_build_main', 'analyze_build_wrapper'] +__all__ = ['scan_build', 'analyze_build', 'analyze_compiler_wrapper'] COMPILER_WRAPPER_CC = 'analyze-cc' COMPILER_WRAPPER_CXX = 'analyze-c++' @command_entry_point -def analyze_build_main(bin_dir, from_build_command): - """ Entry point for 'analyze-build' and 'scan-build'. """ - - parser = create_parser(from_build_command) - args = parser.parse_args() - validate(parser, args, from_build_command) - - # setup logging - initialize_logging(args.verbose) - logging.debug('Parsed arguments: %s', args) - - with report_directory(args.output, args.keep_empty) as target_dir: - if not from_build_command: - # run analyzer only and generate cover report - run_analyzer(args, target_dir) - number_of_bugs = document(args, target_dir, True) - return number_of_bugs if args.status_bugs else 0 - elif args.intercept_first: - # run build command and capture compiler executions - exit_code = capture(args, bin_dir) - # next step to run the analyzer against the captured commands +def scan_build(): + """ Entry point for scan-build command. """ + + args = parse_args_for_scan_build() + # will re-assign the report directory as new output + with report_directory(args.output, args.keep_empty) as args.output: + # Run against a build command. there are cases, when analyzer run + # is not required. But we need to set up everything for the + # wrappers, because 'configure' needs to capture the CC/CXX values + # for the Makefile. + if args.intercept_first: + # Run build command with intercept module. + exit_code = capture(args) + # Run the analyzer against the captured commands. if need_analyzer(args.build): - run_analyzer(args, target_dir) - # cover report generation and bug counting - number_of_bugs = document(args, target_dir, True) - # remove the compilation database when it was not requested - if os.path.exists(args.cdb): - os.unlink(args.cdb) - # set exit status as it was requested - return number_of_bugs if args.status_bugs else exit_code - else: - return exit_code + run_analyzer_parallel(args) else: - # run the build command with compiler wrappers which - # execute the analyzer too. (interposition) - environment = setup_environment(args, target_dir, bin_dir) - logging.debug('run build in environment: %s', environment) - exit_code = subprocess.call(args.build, env=environment) - logging.debug('build finished with exit code: %d', exit_code) - # cover report generation and bug counting - number_of_bugs = document(args, target_dir, False) - # set exit status as it was requested - return number_of_bugs if args.status_bugs else exit_code + # Run build command and analyzer with compiler wrappers. + environment = setup_environment(args) + exit_code = run_build(args.build, env=environment) + # Cover report generation and bug counting. + number_of_bugs = document(args) + # Set exit status as it was requested. + return number_of_bugs if args.status_bugs else exit_code + + +@command_entry_point +def analyze_build(): + """ Entry point for analyze-build command. """ + + args = parse_args_for_analyze_build() + # will re-assign the report directory as new output + with report_directory(args.output, args.keep_empty) as args.output: + # Run the analyzer against a compilation db. + run_analyzer_parallel(args) + # Cover report generation and bug counting. + number_of_bugs = document(args) + # Set exit status as it was requested. + return number_of_bugs if args.status_bugs else 0 def need_analyzer(args): @@ -92,7 +95,7 @@ def need_analyzer(args): return len(args) and not re.search('configure|autogen', args[0]) -def run_analyzer(args, output_dir): +def run_analyzer_parallel(args): """ Runs the analyzer against the given compilation database. """ def exclude(filename): @@ -102,7 +105,7 @@ def run_analyzer(args, output_dir): consts = { 'clang': args.clang, - 'output_dir': output_dir, + 'output_dir': args.output, 'output_format': args.output_format, 'output_failures': args.output_failures, 'direct_args': analyzer_params(args), @@ -124,18 +127,16 @@ def run_analyzer(args, output_dir): pool.join() -def setup_environment(args, destination, bin_dir): +def setup_environment(args): """ Set up environment for build command to interpose compiler wrapper. """ environment = dict(os.environ) + environment.update(wrapper_environment(args)) environment.update({ - 'CC': os.path.join(bin_dir, COMPILER_WRAPPER_CC), - 'CXX': os.path.join(bin_dir, COMPILER_WRAPPER_CXX), - 'ANALYZE_BUILD_CC': args.cc, - 'ANALYZE_BUILD_CXX': args.cxx, + 'CC': COMPILER_WRAPPER_CC, + 'CXX': COMPILER_WRAPPER_CXX, 'ANALYZE_BUILD_CLANG': args.clang if need_analyzer(args.build) else '', - 'ANALYZE_BUILD_VERBOSE': 'DEBUG' if args.verbose > 2 else 'WARNING', - 'ANALYZE_BUILD_REPORT_DIR': destination, + 'ANALYZE_BUILD_REPORT_DIR': args.output, 'ANALYZE_BUILD_REPORT_FORMAT': args.output_format, 'ANALYZE_BUILD_REPORT_FAILURES': 'yes' if args.output_failures else '', 'ANALYZE_BUILD_PARAMETERS': ' '.join(analyzer_params(args)), @@ -144,51 +145,78 @@ def setup_environment(args, destination, bin_dir): return environment -def analyze_build_wrapper(cplusplus): +@command_entry_point +def analyze_compiler_wrapper(): """ Entry point for `analyze-cc` and `analyze-c++` compiler wrappers. """ - # initialize wrapper logging - logging.basicConfig(format='analyze: %(levelname)s: %(message)s', - level=os.getenv('ANALYZE_BUILD_VERBOSE', 'INFO')) - # execute with real compiler - compiler = os.getenv('ANALYZE_BUILD_CXX', 'c++') if cplusplus \ - else os.getenv('ANALYZE_BUILD_CC', 'cc') - compilation = [compiler] + sys.argv[1:] - logging.info('execute compiler: %s', compilation) - result = subprocess.call(compilation) - # exit when it fails, ... + return compiler_wrapper(analyze_compiler_wrapper_impl) + + +def analyze_compiler_wrapper_impl(result, execution): + """ Implements analyzer compiler wrapper functionality. """ + + # don't run analyzer when compilation fails. or when it's not requested. if result or not os.getenv('ANALYZE_BUILD_CLANG'): - return result - # ... and run the analyzer if all went well. + return + + # check is it a compilation? + compilation = split_command(execution.cmd) + if compilation is None: + return + # collect the needed parameters from environment, crash when missing + parameters = { + 'clang': os.getenv('ANALYZE_BUILD_CLANG'), + 'output_dir': os.getenv('ANALYZE_BUILD_REPORT_DIR'), + 'output_format': os.getenv('ANALYZE_BUILD_REPORT_FORMAT'), + 'output_failures': os.getenv('ANALYZE_BUILD_REPORT_FAILURES'), + 'direct_args': os.getenv('ANALYZE_BUILD_PARAMETERS', + '').split(' '), + 'force_debug': os.getenv('ANALYZE_BUILD_FORCE_DEBUG'), + 'directory': execution.cwd, + 'command': [execution.cmd[0], '-c'] + compilation.flags + } + # call static analyzer against the compilation + for source in compilation.files: + parameters.update({'file': source}) + logging.debug('analyzer parameters %s', parameters) + current = run(parameters) + # display error message from the static analyzer + if current is not None: + for line in current['error_output']: + logging.info(line.rstrip()) + + +@contextlib.contextmanager +def report_directory(hint, keep): + """ Responsible for the report directory. + + hint -- could specify the parent directory of the output directory. + keep -- a boolean value to keep or delete the empty report directory. """ + + stamp_format = 'scan-build-%Y-%m-%d-%H-%M-%S-%f-' + stamp = datetime.datetime.now().strftime(stamp_format) + parent_dir = os.path.abspath(hint) + if not os.path.exists(parent_dir): + os.makedirs(parent_dir) + name = tempfile.mkdtemp(prefix=stamp, dir=parent_dir) + + logging.info('Report directory created: %s', name) + try: - # check is it a compilation - compilation = split_command(sys.argv) - if compilation is None: - return result - # collect the needed parameters from environment, crash when missing - parameters = { - 'clang': os.getenv('ANALYZE_BUILD_CLANG'), - 'output_dir': os.getenv('ANALYZE_BUILD_REPORT_DIR'), - 'output_format': os.getenv('ANALYZE_BUILD_REPORT_FORMAT'), - 'output_failures': os.getenv('ANALYZE_BUILD_REPORT_FAILURES'), - 'direct_args': os.getenv('ANALYZE_BUILD_PARAMETERS', - '').split(' '), - 'force_debug': os.getenv('ANALYZE_BUILD_FORCE_DEBUG'), - 'directory': os.getcwd(), - 'command': [sys.argv[0], '-c'] + compilation.flags - } - # call static analyzer against the compilation - for source in compilation.files: - parameters.update({'file': source}) - logging.debug('analyzer parameters %s', parameters) - current = run(parameters) - # display error message from the static analyzer - if current is not None: - for line in current['error_output']: - logging.info(line.rstrip()) - except Exception: - logging.exception("run analyzer inside compiler wrapper failed.") - return result + yield name + finally: + if os.listdir(name): + msg = "Run 'scan-view %s' to examine bug reports." + keep = True + else: + if keep: + msg = "Report directory '%s' contains no report, but kept." + else: + msg = "Removing directory '%s' because it contains no report." + logging.warning(msg, name) + + if not keep: + os.rmdir(name) def analyzer_params(args): @@ -238,279 +266,275 @@ def analyzer_params(args): return prefix_with('-Xclang', result) -def print_active_checkers(checkers): - """ Print active checkers to stdout. """ +def require(required): + """ Decorator for checking the required values in state. - for name in sorted(name for name, (_, active) in checkers.items() - if active): - print(name) + It checks the required attributes in the passed state and stop when + any of those is missing. """ + def decorator(function): + @functools.wraps(function) + def wrapper(*args, **kwargs): + for key in required: + if key not in args[0]: + raise KeyError('{0} not passed to {1}'.format( + key, function.__name__)) -def print_checkers(checkers): - """ Print verbose checker help to stdout. """ + return function(*args, **kwargs) - print('') - print('available checkers:') - print('') - for name in sorted(checkers.keys()): - description, active = checkers[name] - prefix = '+' if active else ' ' - if len(name) > 30: - print(' {0} {1}'.format(prefix, name)) - print(' ' * 35 + description) + return wrapper + + return decorator + + +@require(['command', # entry from compilation database + 'directory', # entry from compilation database + 'file', # entry from compilation database + 'clang', # clang executable name (and path) + 'direct_args', # arguments from command line + 'force_debug', # kill non debug macros + 'output_dir', # where generated report files shall go + 'output_format', # it's 'plist' or 'html' or both + 'output_failures']) # generate crash reports or not +def run(opts): + """ Entry point to run (or not) static analyzer against a single entry + of the compilation database. + + This complex task is decomposed into smaller methods which are calling + each other in chain. If the analyzis is not possibe the given method + just return and break the chain. + + The passed parameter is a python dictionary. Each method first check + that the needed parameters received. (This is done by the 'require' + decorator. It's like an 'assert' to check the contract between the + caller and the called method.) """ + + try: + command = opts.pop('command') + command = command if isinstance(command, list) else decode(command) + logging.debug("Run analyzer against '%s'", command) + opts.update(classify_parameters(command)) + + return arch_check(opts) + except Exception: + logging.error("Problem occured during analyzis.", exc_info=1) + return None + + +@require(['clang', 'directory', 'flags', 'file', 'output_dir', 'language', + 'error_output', 'exit_code']) +def report_failure(opts): + """ Create report when analyzer failed. + + The major report is the preprocessor output. The output filename generated + randomly. The compiler output also captured into '.stderr.txt' file. + And some more execution context also saved into '.info.txt' file. """ + + def extension(): + """ Generate preprocessor file extension. """ + + mapping = {'objective-c++': '.mii', 'objective-c': '.mi', 'c++': '.ii'} + return mapping.get(opts['language'], '.i') + + def destination(): + """ Creates failures directory if not exits yet. """ + + failures_dir = os.path.join(opts['output_dir'], 'failures') + if not os.path.isdir(failures_dir): + os.makedirs(failures_dir) + return failures_dir + + # Classify error type: when Clang terminated by a signal it's a 'Crash'. + # (python subprocess Popen.returncode is negative when child terminated + # by signal.) Everything else is 'Other Error'. + error = 'crash' if opts['exit_code'] < 0 else 'other_error' + # Create preprocessor output file name. (This is blindly following the + # Perl implementation.) + (handle, name) = tempfile.mkstemp(suffix=extension(), + prefix='clang_' + error + '_', + dir=destination()) + os.close(handle) + # Execute Clang again, but run the syntax check only. + cwd = opts['directory'] + cmd = get_arguments( + [opts['clang'], '-fsyntax-only', '-E' + ] + opts['flags'] + [opts['file'], '-o', name], cwd) + run_command(cmd, cwd=cwd) + # write general information about the crash + with open(name + '.info.txt', 'w') as handle: + handle.write(opts['file'] + os.linesep) + handle.write(error.title().replace('_', ' ') + os.linesep) + handle.write(' '.join(cmd) + os.linesep) + handle.write(' '.join(os.uname()) + os.linesep) + handle.write(get_version(opts['clang'])) + handle.close() + # write the captured output too + with open(name + '.stderr.txt', 'w') as handle: + handle.writelines(opts['error_output']) + handle.close() + + +@require(['clang', 'directory', 'flags', 'direct_args', 'file', 'output_dir', + 'output_format']) +def run_analyzer(opts, continuation=report_failure): + """ It assembles the analysis command line and executes it. Capture the + output of the analysis and returns with it. If failure reports are + requested, it calls the continuation to generate it. """ + + def target(): + """ Creates output file name for reports. """ + if opts['output_format'] in {'plist', 'plist-html'}: + (handle, name) = tempfile.mkstemp(prefix='report-', + suffix='.plist', + dir=opts['output_dir']) + os.close(handle) + return name + return opts['output_dir'] + + try: + cwd = opts['directory'] + cmd = get_arguments([opts['clang'], '--analyze'] + + opts['direct_args'] + opts['flags'] + + [opts['file'], '-o', target()], + cwd) + output = run_command(cmd, cwd=cwd) + return {'error_output': output, 'exit_code': 0} + except subprocess.CalledProcessError as ex: + result = {'error_output': ex.output, 'exit_code': ex.returncode} + if opts.get('output_failures', False): + opts.update(result) + continuation(opts) + return result + + +@require(['flags', 'force_debug']) +def filter_debug_flags(opts, continuation=run_analyzer): + """ Filter out nondebug macros when requested. """ + + if opts.pop('force_debug'): + # lazy implementation just append an undefine macro at the end + opts.update({'flags': opts['flags'] + ['-UNDEBUG']}) + + return continuation(opts) + + +@require(['language', 'compiler', 'file', 'flags']) +def language_check(opts, continuation=filter_debug_flags): + """ Find out the language from command line parameters or file name + extension. The decision also influenced by the compiler invocation. """ + + accepted = frozenset({ + 'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output', + 'c++-cpp-output', 'objective-c-cpp-output' + }) + + # language can be given as a parameter... + language = opts.pop('language') + compiler = opts.pop('compiler') + # ... or find out from source file extension + if language is None and compiler is not None: + language = classify_source(opts['file'], compiler == 'c') + + if language is None: + logging.debug('skip analysis, language not known') + return None + elif language not in accepted: + logging.debug('skip analysis, language not supported') + return None + else: + logging.debug('analysis, language: %s', language) + opts.update({'language': language, + 'flags': ['-x', language] + opts['flags']}) + return continuation(opts) + + +@require(['arch_list', 'flags']) +def arch_check(opts, continuation=language_check): + """ Do run analyzer through one of the given architectures. """ + + disabled = frozenset({'ppc', 'ppc64'}) + + received_list = opts.pop('arch_list') + if received_list: + # filter out disabled architectures and -arch switches + filtered_list = [a for a in received_list if a not in disabled] + if filtered_list: + # There should be only one arch given (or the same multiple + # times). If there are multiple arch are given and are not + # the same, those should not change the pre-processing step. + # But that's the only pass we have before run the analyzer. + current = filtered_list.pop() + logging.debug('analysis, on arch: %s', current) + + opts.update({'flags': ['-arch', current] + opts['flags']}) + return continuation(opts) else: - print(' {0} {1: <30} {2}'.format(prefix, name, description)) - print('') - print('NOTE: "+" indicates that an analysis is enabled by default.') - print('') - - -def validate(parser, args, from_build_command): - """ Validation done by the parser itself, but semantic check still - needs to be done. This method is doing that. """ - - # Make plugins always a list. (It might be None when not specified.) - args.plugins = args.plugins if args.plugins else [] - - if args.help_checkers_verbose: - print_checkers(get_checkers(args.clang, args.plugins)) - parser.exit() - elif args.help_checkers: - print_active_checkers(get_checkers(args.clang, args.plugins)) - parser.exit() - - if from_build_command and not args.build: - parser.error('missing build command') - - -def create_parser(from_build_command): - """ Command line argument parser factory method. """ - - parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - - parser.add_argument( - '--verbose', '-v', - action='count', - default=0, - help="""Enable verbose output from '%(prog)s'. A second and third - flag increases verbosity.""") - parser.add_argument( - '--override-compiler', - action='store_true', - help="""Always resort to the compiler wrapper even when better - interposition methods are available.""") - parser.add_argument( - '--intercept-first', - action='store_true', - help="""Run the build commands only, build a compilation database, - then run the static analyzer afterwards. - Generally speaking it has better coverage on build commands. - With '--override-compiler' it use compiler wrapper, but does - not run the analyzer till the build is finished. """) - parser.add_argument( - '--cdb', - metavar='<file>', - default="compile_commands.json", - help="""The JSON compilation database.""") - - parser.add_argument( - '--output', '-o', - metavar='<path>', - default=tempdir(), - help="""Specifies the output directory for analyzer reports. - Subdirectory will be created if default directory is targeted. - """) - parser.add_argument( - '--status-bugs', - action='store_true', - help="""By default, the exit status of '%(prog)s' is the same as the - executed build command. Specifying this option causes the exit - status of '%(prog)s' to be non zero if it found potential bugs - and zero otherwise.""") - parser.add_argument( - '--html-title', - metavar='<title>', - help="""Specify the title used on generated HTML pages. - If not specified, a default title will be used.""") - parser.add_argument( - '--analyze-headers', - action='store_true', - help="""Also analyze functions in #included files. By default, such - functions are skipped unless they are called by functions - within the main source file.""") - format_group = parser.add_mutually_exclusive_group() - format_group.add_argument( - '--plist', '-plist', - dest='output_format', - const='plist', - default='html', - action='store_const', - help="""This option outputs the results as a set of .plist files.""") - format_group.add_argument( - '--plist-html', '-plist-html', - dest='output_format', - const='plist-html', - default='html', - action='store_const', - help="""This option outputs the results as a set of .html and .plist - files.""") - # TODO: implement '-view ' - - advanced = parser.add_argument_group('advanced options') - advanced.add_argument( - '--keep-empty', - action='store_true', - help="""Don't remove the build results directory even if no issues - were reported.""") - advanced.add_argument( - '--no-failure-reports', '-no-failure-reports', - dest='output_failures', - action='store_false', - help="""Do not create a 'failures' subdirectory that includes analyzer - crash reports and preprocessed source files.""") - advanced.add_argument( - '--stats', '-stats', - action='store_true', - help="""Generates visitation statistics for the project being analyzed. - """) - advanced.add_argument( - '--internal-stats', - action='store_true', - help="""Generate internal analyzer statistics.""") - advanced.add_argument( - '--maxloop', '-maxloop', - metavar='<loop count>', - type=int, - help="""Specifiy the number of times a block can be visited before - giving up. Increase for more comprehensive coverage at a cost - of speed.""") - advanced.add_argument( - '--store', '-store', - metavar='<model>', - dest='store_model', - choices=['region', 'basic'], - help="""Specify the store model used by the analyzer. - 'region' specifies a field- sensitive store model. - 'basic' which is far less precise but can more quickly - analyze code. 'basic' was the default store model for - checker-0.221 and earlier.""") - advanced.add_argument( - '--constraints', '-constraints', - metavar='<model>', - dest='constraints_model', - choices=['range', 'basic'], - help="""Specify the contraint engine used by the analyzer. Specifying - 'basic' uses a simpler, less powerful constraint model used by - checker-0.160 and earlier.""") - advanced.add_argument( - '--use-analyzer', - metavar='<path>', - dest='clang', - default='clang', - help="""'%(prog)s' uses the 'clang' executable relative to itself for - static analysis. One can override this behavior with this - option by using the 'clang' packaged with Xcode (on OS X) or - from the PATH.""") - advanced.add_argument( - '--use-cc', - metavar='<path>', - dest='cc', - default='cc', - help="""When '%(prog)s' analyzes a project by interposing a "fake - compiler", which executes a real compiler for compilation and - do other tasks (to run the static analyzer or just record the - compiler invocation). Because of this interposing, '%(prog)s' - does not know what compiler your project normally uses. - Instead, it simply overrides the CC environment variable, and - guesses your default compiler. - - If you need '%(prog)s' to use a specific compiler for - *compilation* then you can use this option to specify a path - to that compiler.""") - advanced.add_argument( - '--use-c++', - metavar='<path>', - dest='cxx', - default='c++', - help="""This is the same as "--use-cc" but for C++ code.""") - advanced.add_argument( - '--analyzer-config', '-analyzer-config', - metavar='<options>', - help="""Provide options to pass through to the analyzer's - -analyzer-config flag. Several options are separated with - comma: 'key1=val1,key2=val2' - - Available options: - stable-report-filename=true or false (default) - - Switch the page naming to: - report-<filename>-<function/method name>-<id>.html - instead of report-XXXXXX.html""") - advanced.add_argument( - '--exclude', - metavar='<directory>', - dest='excludes', - action='append', - default=[], - help="""Do not run static analyzer against files found in this - directory. (You can specify this option multiple times.) - Could be usefull when project contains 3rd party libraries. - The directory path shall be absolute path as file names in - the compilation database.""") - advanced.add_argument( - '--force-analyze-debug-code', - dest='force_debug', - action='store_true', - help="""Tells analyzer to enable assertions in code even if they were - disabled during compilation, enabling more precise results.""") - - plugins = parser.add_argument_group('checker options') - plugins.add_argument( - '--load-plugin', '-load-plugin', - metavar='<plugin library>', - dest='plugins', - action='append', - help="""Loading external checkers using the clang plugin interface.""") - plugins.add_argument( - '--enable-checker', '-enable-checker', - metavar='<checker name>', - action=AppendCommaSeparated, - help="""Enable specific checker.""") - plugins.add_argument( - '--disable-checker', '-disable-checker', - metavar='<checker name>', - action=AppendCommaSeparated, - help="""Disable specific checker.""") - plugins.add_argument( - '--help-checkers', - action='store_true', - help="""A default group of checkers is run unless explicitly disabled. - Exactly which checkers constitute the default group is a - function of the operating system in use. These can be printed - with this flag.""") - plugins.add_argument( - '--help-checkers-verbose', - action='store_true', - help="""Print all available checkers and mark the enabled ones.""") - - if from_build_command: - parser.add_argument( - dest='build', - nargs=argparse.REMAINDER, - help="""Command to run.""") - - return parser - - -class AppendCommaSeparated(argparse.Action): - """ argparse Action class to support multiple comma separated lists. """ - - def __call__(self, __parser, namespace, values, __option_string): - # getattr(obj, attr, default) does not really returns default but none - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, []) - # once it's fixed we can use as expected - actual = getattr(namespace, self.dest) - actual.extend(values.split(',')) - setattr(namespace, self.dest, actual) + logging.debug('skip analysis, found not supported arch') + return None + else: + logging.debug('analysis, on default arch') + return continuation(opts) + +# To have good results from static analyzer certain compiler options shall be +# omitted. The compiler flag filtering only affects the static analyzer run. +# +# Keys are the option name, value number of options to skip +IGNORED_FLAGS = { + '-c': 0, # compile option will be overwritten + '-fsyntax-only': 0, # static analyzer option will be overwritten + '-o': 1, # will set up own output file + # flags below are inherited from the perl implementation. + '-g': 0, + '-save-temps': 0, + '-install_name': 1, + '-exported_symbols_list': 1, + '-current_version': 1, + '-compatibility_version': 1, + '-init': 1, + '-e': 1, + '-seg1addr': 1, + '-bundle_loader': 1, + '-multiply_defined': 1, + '-sectorder': 3, + '--param': 1, + '--serialize-diagnostics': 1 +} + + +def classify_parameters(command): + """ Prepare compiler flags (filters some and add others) and take out + language (-x) and architecture (-arch) flags for future processing. """ + + result = { + 'flags': [], # the filtered compiler flags + 'arch_list': [], # list of architecture flags + 'language': None, # compilation language, None, if not specified + 'compiler': compiler_language(command) # 'c' or 'c++' + } + + # iterate on the compile options + args = iter(command[1:]) + for arg in args: + # take arch flags into a separate basket + if arg == '-arch': + result['arch_list'].append(next(args)) + # take language + elif arg == '-x': + result['language'] = next(args) + # parameters which looks source file are not flags + elif re.match(r'^[^-].+', arg) and classify_source(arg): + pass + # ignore some flags + elif arg in IGNORED_FLAGS: + count = IGNORED_FLAGS[arg] + for _ in range(count): + next(args) + # we don't care about extra warnings, but we should suppress ones + # that we don't want to see. + elif re.match(r'^-W.+', arg) and not re.match(r'^-Wno-.+', arg): + pass + # and consider everything else as compilation flag. + else: + result['flags'].append(arg) + + return result |