summaryrefslogtreecommitdiff
path: root/tools/scan-build-py/libscanbuild/analyze.py
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2017-04-16 16:02:28 +0000
committerDimitry Andric <dim@FreeBSD.org>2017-04-16 16:02:28 +0000
commit7442d6faa2719e4e7d33a7021c406c5a4facd74d (patch)
treec72b9241553fc9966179aba84f90f17bfa9235c3 /tools/scan-build-py/libscanbuild/analyze.py
parentb52119637f743680a99710ce5fdb6646da2772af (diff)
Diffstat (limited to 'tools/scan-build-py/libscanbuild/analyze.py')
-rw-r--r--tools/scan-build-py/libscanbuild/analyze.py766
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