aboutsummaryrefslogtreecommitdiff
path: root/tools/scan-build-py/libscanbuild/arguments.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/scan-build-py/libscanbuild/arguments.py')
-rw-r--r--tools/scan-build-py/libscanbuild/arguments.py431
1 files changed, 431 insertions, 0 deletions
diff --git a/tools/scan-build-py/libscanbuild/arguments.py b/tools/scan-build-py/libscanbuild/arguments.py
new file mode 100644
index 000000000000..2735123f9f16
--- /dev/null
+++ b/tools/scan-build-py/libscanbuild/arguments.py
@@ -0,0 +1,431 @@
+# -*- 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 parses and validates arguments for command-line interfaces.
+
+It uses argparse module to create the command line parser. (This library is
+in the standard python library since 3.2 and backported to 2.7, but not
+earlier.)
+
+It also implements basic validation methods, related to the command.
+Validations are mostly calling specific help methods, or mangling values.
+"""
+
+import os
+import sys
+import argparse
+import logging
+import tempfile
+from libscanbuild import reconfigure_logging
+from libscanbuild.clang import get_checkers
+
+__all__ = ['parse_args_for_intercept_build', 'parse_args_for_analyze_build',
+ 'parse_args_for_scan_build']
+
+
+def parse_args_for_intercept_build():
+ """ Parse and validate command-line arguments for intercept-build. """
+
+ parser = create_intercept_parser()
+ args = parser.parse_args()
+
+ reconfigure_logging(args.verbose)
+ logging.debug('Raw arguments %s', sys.argv)
+
+ # short validation logic
+ if not args.build:
+ parser.error(message='missing build command')
+
+ logging.debug('Parsed arguments: %s', args)
+ return args
+
+
+def parse_args_for_analyze_build():
+ """ Parse and validate command-line arguments for analyze-build. """
+
+ from_build_command = False
+ parser = create_analyze_parser(from_build_command)
+ args = parser.parse_args()
+
+ reconfigure_logging(args.verbose)
+ logging.debug('Raw arguments %s', sys.argv)
+
+ normalize_args_for_analyze(args, from_build_command)
+ validate_args_for_analyze(parser, args, from_build_command)
+ logging.debug('Parsed arguments: %s', args)
+ return args
+
+
+def parse_args_for_scan_build():
+ """ Parse and validate command-line arguments for scan-build. """
+
+ from_build_command = True
+ parser = create_analyze_parser(from_build_command)
+ args = parser.parse_args()
+
+ reconfigure_logging(args.verbose)
+ logging.debug('Raw arguments %s', sys.argv)
+
+ normalize_args_for_analyze(args, from_build_command)
+ validate_args_for_analyze(parser, args, from_build_command)
+ logging.debug('Parsed arguments: %s', args)
+ return args
+
+
+def normalize_args_for_analyze(args, from_build_command):
+ """ Normalize parsed arguments for analyze-build and scan-build.
+
+ :param args: Parsed argument object. (Will be mutated.)
+ :param from_build_command: Boolean value tells is the command suppose
+ to run the analyzer against a build command or a compilation db. """
+
+ # make plugins always a list. (it might be None when not specified.)
+ if args.plugins is None:
+ args.plugins = []
+
+ # make exclude directory list unique and absolute.
+ uniq_excludes = set(os.path.abspath(entry) for entry in args.excludes)
+ args.excludes = list(uniq_excludes)
+
+ # because shared codes for all tools, some common used methods are
+ # expecting some argument to be present. so, instead of query the args
+ # object about the presence of the flag, we fake it here. to make those
+ # methods more readable. (it's an arguable choice, took it only for those
+ # which have good default value.)
+ if from_build_command:
+ # add cdb parameter invisibly to make report module working.
+ args.cdb = 'compile_commands.json'
+
+
+def validate_args_for_analyze(parser, args, from_build_command):
+ """ Command line parsing is done by the argparse module, but semantic
+ validation still needs to be done. This method is doing it for
+ analyze-build and scan-build commands.
+
+ :param parser: The command line parser object.
+ :param args: Parsed argument object.
+ :param from_build_command: Boolean value tells is the command suppose
+ to run the analyzer against a build command or a compilation db.
+ :return: No return value, but this call might throw when validation
+ fails. """
+
+ if args.help_checkers_verbose:
+ print_checkers(get_checkers(args.clang, args.plugins))
+ parser.exit(status=0)
+ elif args.help_checkers:
+ print_active_checkers(get_checkers(args.clang, args.plugins))
+ parser.exit(status=0)
+ elif from_build_command and not args.build:
+ parser.error(message='missing build command')
+ elif not from_build_command and not os.path.exists(args.cdb):
+ parser.error(message='compilation database is missing')
+
+
+def create_intercept_parser():
+ """ Creates a parser for command-line arguments to 'intercept'. """
+
+ parser = create_default_parser()
+ parser_add_cdb(parser)
+
+ parser_add_prefer_wrapper(parser)
+ parser_add_compilers(parser)
+
+ advanced = parser.add_argument_group('advanced options')
+ group = advanced.add_mutually_exclusive_group()
+ group.add_argument(
+ '--append',
+ action='store_true',
+ help="""Extend existing compilation database with new entries.
+ Duplicate entries are detected and not present in the final output.
+ The output is not continuously updated, it's done when the build
+ command finished. """)
+
+ parser.add_argument(
+ dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
+ return parser
+
+
+def create_analyze_parser(from_build_command):
+ """ Creates a parser for command-line arguments to 'analyze'. """
+
+ parser = create_default_parser()
+
+ if from_build_command:
+ parser_add_prefer_wrapper(parser)
+ parser_add_compilers(parser)
+
+ parser.add_argument(
+ '--intercept-first',
+ action='store_true',
+ help="""Run the build commands first, intercept compiler
+ calls and 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.""")
+ else:
+ parser_add_cdb(parser)
+
+ parser.add_argument(
+ '--status-bugs',
+ action='store_true',
+ help="""The exit status of '%(prog)s' is the same as the executed
+ build command. This option ignores the build exit status and sets to
+ be non zero if it found potential bugs or zero otherwise.""")
+ parser.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 useful when project contains 3rd party libraries.""")
+
+ output = parser.add_argument_group('output control options')
+ output.add_argument(
+ '--output',
+ '-o',
+ metavar='<path>',
+ default=tempfile.gettempdir(),
+ help="""Specifies the output directory for analyzer reports.
+ Subdirectory will be created if default directory is targeted.""")
+ output.add_argument(
+ '--keep-empty',
+ action='store_true',
+ help="""Don't remove the build results directory even if no issues
+ were reported.""")
+ output.add_argument(
+ '--html-title',
+ metavar='<title>',
+ help="""Specify the title used on generated HTML pages.
+ If not specified, a default title will be used.""")
+ format_group = output.add_mutually_exclusive_group()
+ format_group.add_argument(
+ '--plist',
+ '-plist',
+ dest='output_format',
+ const='plist',
+ default='html',
+ action='store_const',
+ help="""Cause 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="""Cause the results as a set of .html and .plist files.""")
+ # TODO: implement '-view '
+
+ advanced = parser.add_argument_group('advanced options')
+ 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(
+ '--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.""")
+ 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.""")
+ advanced.add_argument(
+ '--stats',
+ '-stats',
+ action='store_true',
+ help="""Generates visitation statistics for the project.""")
+ 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 constraint engine used by the analyzer. Specifying
+ 'basic' uses a simpler, less powerful constraint model used by
+ checker-0.160 and earlier.""")
+ 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(
+ '--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
+
+
+def create_default_parser():
+ """ Creates command line parser for all build wrapper commands. """
+
+ 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, third and
+ fourth flags increases verbosity.""")
+ return parser
+
+
+def parser_add_cdb(parser):
+ parser.add_argument(
+ '--cdb',
+ metavar='<file>',
+ default="compile_commands.json",
+ help="""The JSON compilation database.""")
+
+
+def parser_add_prefer_wrapper(parser):
+ parser.add_argument(
+ '--override-compiler',
+ action='store_true',
+ help="""Always resort to the compiler wrapper even when better
+ intercept methods are available.""")
+
+
+def parser_add_compilers(parser):
+ parser.add_argument(
+ '--use-cc',
+ metavar='<path>',
+ dest='cc',
+ default=os.getenv('CC', 'cc'),
+ help="""When '%(prog)s' analyzes a project by interposing a compiler
+ wrapper, which executes a real compiler for compilation and do other
+ tasks (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.""")
+ parser.add_argument(
+ '--use-c++',
+ metavar='<path>',
+ dest='cxx',
+ default=os.getenv('CXX', 'c++'),
+ help="""This is the same as "--use-cc" but for C++ code.""")
+
+
+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)
+
+
+def print_active_checkers(checkers):
+ """ Print active checkers to stdout. """
+
+ for name in sorted(name for name, (_, active) in checkers.items()
+ if active):
+ print(name)
+
+
+def print_checkers(checkers):
+ """ Print verbose checker help to stdout. """
+
+ 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)
+ else:
+ print(' {0} {1: <30} {2}'.format(prefix, name, description))
+ print('')
+ print('NOTE: "+" indicates that an analysis is enabled by default.')
+ print('')