1 # -*- coding: utf-8 -*-
2 # The LLVM Compiler Infrastructure
4 # This file is distributed under the University of Illinois Open Source
5 # License. See LICENSE.TXT for details.
6 """ This module parses and validates arguments for command-line interfaces.
8 It uses argparse module to create the command line parser. (This library is
9 in the standard python library since 3.2 and backported to 2.7, but not
12 It also implements basic validation methods, related to the command.
13 Validations are mostly calling specific help methods, or mangling values.
21 from libscanbuild import reconfigure_logging, CtuConfig
22 from libscanbuild.clang import get_checkers, is_ctu_capable
24 __all__ = ['parse_args_for_intercept_build', 'parse_args_for_analyze_build',
25 'parse_args_for_scan_build']
28 def parse_args_for_intercept_build():
29 """ Parse and validate command-line arguments for intercept-build. """
31 parser = create_intercept_parser()
32 args = parser.parse_args()
34 reconfigure_logging(args.verbose)
35 logging.debug('Raw arguments %s', sys.argv)
37 # short validation logic
39 parser.error(message='missing build command')
41 logging.debug('Parsed arguments: %s', args)
45 def parse_args_for_analyze_build():
46 """ Parse and validate command-line arguments for analyze-build. """
48 from_build_command = False
49 parser = create_analyze_parser(from_build_command)
50 args = parser.parse_args()
52 reconfigure_logging(args.verbose)
53 logging.debug('Raw arguments %s', sys.argv)
55 normalize_args_for_analyze(args, from_build_command)
56 validate_args_for_analyze(parser, args, from_build_command)
57 logging.debug('Parsed arguments: %s', args)
61 def parse_args_for_scan_build():
62 """ Parse and validate command-line arguments for scan-build. """
64 from_build_command = True
65 parser = create_analyze_parser(from_build_command)
66 args = parser.parse_args()
68 reconfigure_logging(args.verbose)
69 logging.debug('Raw arguments %s', sys.argv)
71 normalize_args_for_analyze(args, from_build_command)
72 validate_args_for_analyze(parser, args, from_build_command)
73 logging.debug('Parsed arguments: %s', args)
77 def normalize_args_for_analyze(args, from_build_command):
78 """ Normalize parsed arguments for analyze-build and scan-build.
80 :param args: Parsed argument object. (Will be mutated.)
81 :param from_build_command: Boolean value tells is the command suppose
82 to run the analyzer against a build command or a compilation db. """
84 # make plugins always a list. (it might be None when not specified.)
85 if args.plugins is None:
88 # make exclude directory list unique and absolute.
89 uniq_excludes = set(os.path.abspath(entry) for entry in args.excludes)
90 args.excludes = list(uniq_excludes)
92 # because shared codes for all tools, some common used methods are
93 # expecting some argument to be present. so, instead of query the args
94 # object about the presence of the flag, we fake it here. to make those
95 # methods more readable. (it's an arguable choice, took it only for those
96 # which have good default value.)
97 if from_build_command:
98 # add cdb parameter invisibly to make report module working.
99 args.cdb = 'compile_commands.json'
101 # Make ctu_dir an abspath as it is needed inside clang
102 if not from_build_command and hasattr(args, 'ctu_phases') \
103 and hasattr(args.ctu_phases, 'dir'):
104 args.ctu_dir = os.path.abspath(args.ctu_dir)
107 def validate_args_for_analyze(parser, args, from_build_command):
108 """ Command line parsing is done by the argparse module, but semantic
109 validation still needs to be done. This method is doing it for
110 analyze-build and scan-build commands.
112 :param parser: The command line parser object.
113 :param args: Parsed argument object.
114 :param from_build_command: Boolean value tells is the command suppose
115 to run the analyzer against a build command or a compilation db.
116 :return: No return value, but this call might throw when validation
119 if args.help_checkers_verbose:
120 print_checkers(get_checkers(args.clang, args.plugins))
121 parser.exit(status=0)
122 elif args.help_checkers:
123 print_active_checkers(get_checkers(args.clang, args.plugins))
124 parser.exit(status=0)
125 elif from_build_command and not args.build:
126 parser.error(message='missing build command')
127 elif not from_build_command and not os.path.exists(args.cdb):
128 parser.error(message='compilation database is missing')
130 # If the user wants CTU mode
131 if not from_build_command and hasattr(args, 'ctu_phases') \
132 and hasattr(args.ctu_phases, 'dir'):
133 # If CTU analyze_only, the input directory should exist
134 if args.ctu_phases.analyze and not args.ctu_phases.collect \
135 and not os.path.exists(args.ctu_dir):
136 parser.error(message='missing CTU directory')
137 # Check CTU capability via checking clang-func-mapping
138 if not is_ctu_capable(args.func_map_cmd):
139 parser.error(message="""This version of clang does not support CTU
140 functionality or clang-func-mapping command not found.""")
143 def create_intercept_parser():
144 """ Creates a parser for command-line arguments to 'intercept'. """
146 parser = create_default_parser()
147 parser_add_cdb(parser)
149 parser_add_prefer_wrapper(parser)
150 parser_add_compilers(parser)
152 advanced = parser.add_argument_group('advanced options')
153 group = advanced.add_mutually_exclusive_group()
157 help="""Extend existing compilation database with new entries.
158 Duplicate entries are detected and not present in the final output.
159 The output is not continuously updated, it's done when the build
160 command finished. """)
163 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
167 def create_analyze_parser(from_build_command):
168 """ Creates a parser for command-line arguments to 'analyze'. """
170 parser = create_default_parser()
172 if from_build_command:
173 parser_add_prefer_wrapper(parser)
174 parser_add_compilers(parser)
179 help="""Run the build commands first, intercept compiler
180 calls and then run the static analyzer afterwards.
181 Generally speaking it has better coverage on build commands.
182 With '--override-compiler' it use compiler wrapper, but does
183 not run the analyzer till the build is finished.""")
185 parser_add_cdb(parser)
190 help="""The exit status of '%(prog)s' is the same as the executed
191 build command. This option ignores the build exit status and sets to
192 be non zero if it found potential bugs or zero otherwise.""")
195 metavar='<directory>',
199 help="""Do not run static analyzer against files found in this
200 directory. (You can specify this option multiple times.)
201 Could be useful when project contains 3rd party libraries.""")
203 output = parser.add_argument_group('output control options')
208 default=tempfile.gettempdir(),
209 help="""Specifies the output directory for analyzer reports.
210 Subdirectory will be created if default directory is targeted.""")
214 help="""Don't remove the build results directory even if no issues
219 help="""Specify the title used on generated HTML pages.
220 If not specified, a default title will be used.""")
221 format_group = output.add_mutually_exclusive_group()
222 format_group.add_argument(
225 dest='output_format',
228 action='store_const',
229 help="""Cause the results as a set of .plist files.""")
230 format_group.add_argument(
233 dest='output_format',
236 action='store_const',
237 help="""Cause the results as a set of .html and .plist files.""")
238 format_group.add_argument(
239 '--plist-multi-file',
241 dest='output_format',
242 const='plist-multi-file',
244 action='store_const',
245 help="""Cause the results as a set of .plist files with extra
246 information on related files.""")
248 advanced = parser.add_argument_group('advanced options')
249 advanced.add_argument(
254 help="""'%(prog)s' uses the 'clang' executable relative to itself for
255 static analysis. One can override this behavior with this option by
256 using the 'clang' packaged with Xcode (on OS X) or from the PATH.""")
257 advanced.add_argument(
258 '--no-failure-reports',
259 '-no-failure-reports',
260 dest='output_failures',
261 action='store_false',
262 help="""Do not create a 'failures' subdirectory that includes analyzer
263 crash reports and preprocessed source files.""")
267 help="""Also analyze functions in #included files. By default, such
268 functions are skipped unless they are called by functions within the
269 main source file.""")
270 advanced.add_argument(
274 help="""Generates visitation statistics for the project.""")
275 advanced.add_argument(
278 help="""Generate internal analyzer statistics.""")
279 advanced.add_argument(
282 metavar='<loop count>',
284 help="""Specify the number of times a block can be visited before
285 giving up. Increase for more comprehensive coverage at a cost of
287 advanced.add_argument(
292 choices=['region', 'basic'],
293 help="""Specify the store model used by the analyzer. 'region'
294 specifies a field- sensitive store model. 'basic' which is far less
295 precise but can more quickly analyze code. 'basic' was the default
296 store model for checker-0.221 and earlier.""")
297 advanced.add_argument(
301 dest='constraints_model',
302 choices=['range', 'basic'],
303 help="""Specify the constraint engine used by the analyzer. Specifying
304 'basic' uses a simpler, less powerful constraint model used by
305 checker-0.160 and earlier.""")
306 advanced.add_argument(
310 help="""Provide options to pass through to the analyzer's
311 -analyzer-config flag. Several options are separated with comma:
312 'key1=val1,key2=val2'
315 stable-report-filename=true or false (default)
317 Switch the page naming to:
318 report-<filename>-<function/method name>-<id>.html
319 instead of report-XXXXXX.html""")
320 advanced.add_argument(
321 '--force-analyze-debug-code',
324 help="""Tells analyzer to enable assertions in code even if they were
325 disabled during compilation, enabling more precise results.""")
327 plugins = parser.add_argument_group('checker options')
328 plugins.add_argument(
331 metavar='<plugin library>',
334 help="""Loading external checkers using the clang plugin interface.""")
335 plugins.add_argument(
338 metavar='<checker name>',
339 action=AppendCommaSeparated,
340 help="""Enable specific checker.""")
341 plugins.add_argument(
344 metavar='<checker name>',
345 action=AppendCommaSeparated,
346 help="""Disable specific checker.""")
347 plugins.add_argument(
350 help="""A default group of checkers is run unless explicitly disabled.
351 Exactly which checkers constitute the default group is a function of
352 the operating system in use. These can be printed with this flag.""")
353 plugins.add_argument(
354 '--help-checkers-verbose',
356 help="""Print all available checkers and mark the enabled ones.""")
358 if from_build_command:
360 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
362 ctu = parser.add_argument_group('cross translation unit analysis')
363 ctu_mutex_group = ctu.add_mutually_exclusive_group()
364 ctu_mutex_group.add_argument(
366 action='store_const',
367 const=CtuConfig(collect=True, analyze=True,
368 dir='', func_map_cmd=''),
370 help="""Perform cross translation unit (ctu) analysis (both collect
371 and analyze phases) using default <ctu-dir> for temporary output.
372 At the end of the analysis, the temporary directory is removed.""")
378 help="""Defines the temporary directory used between ctu
380 ctu_mutex_group.add_argument(
381 '--ctu-collect-only',
382 action='store_const',
383 const=CtuConfig(collect=True, analyze=False,
384 dir='', func_map_cmd=''),
386 help="""Perform only the collect phase of ctu.
387 Keep <ctu-dir> for further use.""")
388 ctu_mutex_group.add_argument(
389 '--ctu-analyze-only',
390 action='store_const',
391 const=CtuConfig(collect=False, analyze=True,
392 dir='', func_map_cmd=''),
394 help="""Perform only the analyze phase of ctu. <ctu-dir> should be
395 present and will not be removed after analysis.""")
397 '--use-func-map-cmd',
400 default='clang-func-mapping',
401 help="""'%(prog)s' uses the 'clang-func-mapping' executable
402 relative to itself for generating function maps for static
403 analysis. One can override this behavior with this option by using
404 the 'clang-func-mapping' packaged with Xcode (on OS X) or from the
409 def create_default_parser():
410 """ Creates command line parser for all build wrapper commands. """
412 parser = argparse.ArgumentParser(
413 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
420 help="""Enable verbose output from '%(prog)s'. A second, third and
421 fourth flags increases verbosity.""")
425 def parser_add_cdb(parser):
429 default="compile_commands.json",
430 help="""The JSON compilation database.""")
433 def parser_add_prefer_wrapper(parser):
435 '--override-compiler',
437 help="""Always resort to the compiler wrapper even when better
438 intercept methods are available.""")
441 def parser_add_compilers(parser):
446 default=os.getenv('CC', 'cc'),
447 help="""When '%(prog)s' analyzes a project by interposing a compiler
448 wrapper, which executes a real compiler for compilation and do other
449 tasks (record the compiler invocation). Because of this interposing,
450 '%(prog)s' does not know what compiler your project normally uses.
451 Instead, it simply overrides the CC environment variable, and guesses
452 your default compiler.
454 If you need '%(prog)s' to use a specific compiler for *compilation*
455 then you can use this option to specify a path to that compiler.""")
460 default=os.getenv('CXX', 'c++'),
461 help="""This is the same as "--use-cc" but for C++ code.""")
464 class AppendCommaSeparated(argparse.Action):
465 """ argparse Action class to support multiple comma separated lists. """
467 def __call__(self, __parser, namespace, values, __option_string):
468 # getattr(obj, attr, default) does not really returns default but none
469 if getattr(namespace, self.dest, None) is None:
470 setattr(namespace, self.dest, [])
471 # once it's fixed we can use as expected
472 actual = getattr(namespace, self.dest)
473 actual.extend(values.split(','))
474 setattr(namespace, self.dest, actual)
477 def print_active_checkers(checkers):
478 """ Print active checkers to stdout. """
480 for name in sorted(name for name, (_, active) in checkers.items()
485 def print_checkers(checkers):
486 """ Print verbose checker help to stdout. """
489 print('available checkers:')
491 for name in sorted(checkers.keys()):
492 description, active = checkers[name]
493 prefix = '+' if active else ' '
495 print(' {0} {1}'.format(prefix, name))
496 print(' ' * 35 + description)
498 print(' {0} {1: <30} {2}'.format(prefix, name, description))
500 print('NOTE: "+" indicates that an analysis is enabled by default.')