blob: 00679a45937ff575a70e510d14baa267d2718890 [file] [log] [blame]
Laszlo Nagy908ed4c2017-03-08 21:22:32 +00001# -*- coding: utf-8 -*-
2# The LLVM Compiler Infrastructure
3#
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.
7
8It uses argparse module to create the command line parser. (This library is
9in the standard python library since 3.2 and backported to 2.7, but not
10earlier.)
11
12It also implements basic validation methods, related to the command.
13Validations are mostly calling specific help methods, or mangling values.
14"""
15
16import os
17import sys
18import argparse
19import logging
Laszlo Nagy0d9be632017-03-20 09:03:24 +000020import tempfile
Gabor Horvatheb0584b2018-02-28 13:23:10 +000021from libscanbuild import reconfigure_logging, CtuConfig
22from libscanbuild.clang import get_checkers, is_ctu_capable
Laszlo Nagy908ed4c2017-03-08 21:22:32 +000023
24__all__ = ['parse_args_for_intercept_build', 'parse_args_for_analyze_build',
25 'parse_args_for_scan_build']
26
27
28def parse_args_for_intercept_build():
29 """ Parse and validate command-line arguments for intercept-build. """
30
31 parser = create_intercept_parser()
32 args = parser.parse_args()
33
34 reconfigure_logging(args.verbose)
35 logging.debug('Raw arguments %s', sys.argv)
36
37 # short validation logic
38 if not args.build:
39 parser.error(message='missing build command')
40
41 logging.debug('Parsed arguments: %s', args)
42 return args
43
44
45def parse_args_for_analyze_build():
46 """ Parse and validate command-line arguments for analyze-build. """
47
48 from_build_command = False
49 parser = create_analyze_parser(from_build_command)
50 args = parser.parse_args()
51
52 reconfigure_logging(args.verbose)
53 logging.debug('Raw arguments %s', sys.argv)
54
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)
58 return args
59
60
61def parse_args_for_scan_build():
62 """ Parse and validate command-line arguments for scan-build. """
63
64 from_build_command = True
65 parser = create_analyze_parser(from_build_command)
66 args = parser.parse_args()
67
68 reconfigure_logging(args.verbose)
69 logging.debug('Raw arguments %s', sys.argv)
70
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)
74 return args
75
76
77def normalize_args_for_analyze(args, from_build_command):
78 """ Normalize parsed arguments for analyze-build and scan-build.
79
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. """
83
84 # make plugins always a list. (it might be None when not specified.)
85 if args.plugins is None:
86 args.plugins = []
87
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)
91
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'
100
Gabor Horvatheb0584b2018-02-28 13:23:10 +0000101 # 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)
105
Laszlo Nagy908ed4c2017-03-08 21:22:32 +0000106
107def 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.
111
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
117 fails. """
118
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')
129
Gabor Horvatheb0584b2018-02-28 13:23:10 +0000130 # 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.""")
141
Laszlo Nagy908ed4c2017-03-08 21:22:32 +0000142
143def create_intercept_parser():
144 """ Creates a parser for command-line arguments to 'intercept'. """
145
146 parser = create_default_parser()
147 parser_add_cdb(parser)
148
149 parser_add_prefer_wrapper(parser)
150 parser_add_compilers(parser)
151
152 advanced = parser.add_argument_group('advanced options')
153 group = advanced.add_mutually_exclusive_group()
154 group.add_argument(
155 '--append',
156 action='store_true',
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. """)
161
162 parser.add_argument(
163 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
164 return parser
165
166
167def create_analyze_parser(from_build_command):
168 """ Creates a parser for command-line arguments to 'analyze'. """
169
170 parser = create_default_parser()
171
172 if from_build_command:
173 parser_add_prefer_wrapper(parser)
174 parser_add_compilers(parser)
175
176 parser.add_argument(
177 '--intercept-first',
178 action='store_true',
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.""")
184 else:
185 parser_add_cdb(parser)
186
187 parser.add_argument(
188 '--status-bugs',
189 action='store_true',
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.""")
193 parser.add_argument(
194 '--exclude',
195 metavar='<directory>',
196 dest='excludes',
197 action='append',
198 default=[],
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.""")
202
203 output = parser.add_argument_group('output control options')
204 output.add_argument(
205 '--output',
206 '-o',
207 metavar='<path>',
Laszlo Nagy0d9be632017-03-20 09:03:24 +0000208 default=tempfile.gettempdir(),
Laszlo Nagy908ed4c2017-03-08 21:22:32 +0000209 help="""Specifies the output directory for analyzer reports.
210 Subdirectory will be created if default directory is targeted.""")
211 output.add_argument(
212 '--keep-empty',
213 action='store_true',
214 help="""Don't remove the build results directory even if no issues
215 were reported.""")
216 output.add_argument(
217 '--html-title',
218 metavar='<title>',
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(
223 '--plist',
224 '-plist',
225 dest='output_format',
226 const='plist',
227 default='html',
228 action='store_const',
229 help="""Cause the results as a set of .plist files.""")
230 format_group.add_argument(
231 '--plist-html',
232 '-plist-html',
233 dest='output_format',
234 const='plist-html',
235 default='html',
236 action='store_const',
237 help="""Cause the results as a set of .html and .plist files.""")
Gabor Horvatheb0584b2018-02-28 13:23:10 +0000238 format_group.add_argument(
239 '--plist-multi-file',
240 '-plist-multi-file',
241 dest='output_format',
242 const='plist-multi-file',
243 default='html',
244 action='store_const',
245 help="""Cause the results as a set of .plist files with extra
246 information on related files.""")
Laszlo Nagy908ed4c2017-03-08 21:22:32 +0000247
248 advanced = parser.add_argument_group('advanced options')
249 advanced.add_argument(
250 '--use-analyzer',
251 metavar='<path>',
252 dest='clang',
253 default='clang',
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.""")
264 parser.add_argument(
265 '--analyze-headers',
266 action='store_true',
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(
271 '--stats',
272 '-stats',
273 action='store_true',
274 help="""Generates visitation statistics for the project.""")
275 advanced.add_argument(
276 '--internal-stats',
277 action='store_true',
278 help="""Generate internal analyzer statistics.""")
279 advanced.add_argument(
280 '--maxloop',
281 '-maxloop',
282 metavar='<loop count>',
283 type=int,
284 help="""Specifiy the number of times a block can be visited before
285 giving up. Increase for more comprehensive coverage at a cost of
286 speed.""")
287 advanced.add_argument(
288 '--store',
289 '-store',
290 metavar='<model>',
291 dest='store_model',
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(
298 '--constraints',
299 '-constraints',
300 metavar='<model>',
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(
307 '--analyzer-config',
308 '-analyzer-config',
309 metavar='<options>',
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'
313
314 Available options:
315 stable-report-filename=true or false (default)
316
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',
322 dest='force_debug',
323 action='store_true',
324 help="""Tells analyzer to enable assertions in code even if they were
325 disabled during compilation, enabling more precise results.""")
326
327 plugins = parser.add_argument_group('checker options')
328 plugins.add_argument(
329 '--load-plugin',
330 '-load-plugin',
331 metavar='<plugin library>',
332 dest='plugins',
333 action='append',
334 help="""Loading external checkers using the clang plugin interface.""")
335 plugins.add_argument(
336 '--enable-checker',
337 '-enable-checker',
338 metavar='<checker name>',
339 action=AppendCommaSeparated,
340 help="""Enable specific checker.""")
341 plugins.add_argument(
342 '--disable-checker',
343 '-disable-checker',
344 metavar='<checker name>',
345 action=AppendCommaSeparated,
346 help="""Disable specific checker.""")
347 plugins.add_argument(
348 '--help-checkers',
349 action='store_true',
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',
355 action='store_true',
356 help="""Print all available checkers and mark the enabled ones.""")
357
358 if from_build_command:
359 parser.add_argument(
360 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
Gabor Horvatheb0584b2018-02-28 13:23:10 +0000361 else:
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(
365 '--ctu',
366 action='store_const',
367 const=CtuConfig(collect=True, analyze=True,
368 dir='', func_map_cmd=''),
369 dest='ctu_phases',
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.""")
373 ctu.add_argument(
374 '--ctu-dir',
375 metavar='<ctu-dir>',
376 dest='ctu_dir',
377 default='ctu-dir',
378 help="""Defines the temporary directory used between ctu
379 phases.""")
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=''),
385 dest='ctu_phases',
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=''),
393 dest='ctu_phases',
394 help="""Perform only the analyze phase of ctu. <ctu-dir> should be
395 present and will not be removed after analysis.""")
396 ctu.add_argument(
397 '--use-func-map-cmd',
398 metavar='<path>',
399 dest='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
405 PATH.""")
Laszlo Nagy908ed4c2017-03-08 21:22:32 +0000406 return parser
407
408
409def create_default_parser():
410 """ Creates command line parser for all build wrapper commands. """
411
412 parser = argparse.ArgumentParser(
413 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
414
415 parser.add_argument(
416 '--verbose',
417 '-v',
418 action='count',
419 default=0,
420 help="""Enable verbose output from '%(prog)s'. A second, third and
421 fourth flags increases verbosity.""")
422 return parser
423
424
425def parser_add_cdb(parser):
426 parser.add_argument(
427 '--cdb',
428 metavar='<file>',
429 default="compile_commands.json",
430 help="""The JSON compilation database.""")
431
432
433def parser_add_prefer_wrapper(parser):
434 parser.add_argument(
435 '--override-compiler',
436 action='store_true',
437 help="""Always resort to the compiler wrapper even when better
438 intercept methods are available.""")
439
440
441def parser_add_compilers(parser):
442 parser.add_argument(
443 '--use-cc',
444 metavar='<path>',
445 dest='cc',
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.
453
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.""")
456 parser.add_argument(
457 '--use-c++',
458 metavar='<path>',
459 dest='cxx',
460 default=os.getenv('CXX', 'c++'),
461 help="""This is the same as "--use-cc" but for C++ code.""")
462
463
464class AppendCommaSeparated(argparse.Action):
465 """ argparse Action class to support multiple comma separated lists. """
466
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)
475
476
477def print_active_checkers(checkers):
478 """ Print active checkers to stdout. """
479
480 for name in sorted(name for name, (_, active) in checkers.items()
481 if active):
482 print(name)
483
484
485def print_checkers(checkers):
486 """ Print verbose checker help to stdout. """
487
488 print('')
489 print('available checkers:')
490 print('')
491 for name in sorted(checkers.keys()):
492 description, active = checkers[name]
493 prefix = '+' if active else ' '
494 if len(name) > 30:
495 print(' {0} {1}'.format(prefix, name))
496 print(' ' * 35 + description)
497 else:
498 print(' {0} {1: <30} {2}'.format(prefix, name, description))
499 print('')
500 print('NOTE: "+" indicates that an analysis is enabled by default.')
501 print('')