blob: 2735123f9f163a54561f405fa1b9bb82803458e4 [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
21from libscanbuild import reconfigure_logging
Laszlo Nagy908ed4c2017-03-08 21:22:32 +000022from libscanbuild.clang import get_checkers
23
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
101
102def validate_args_for_analyze(parser, args, from_build_command):
103 """ Command line parsing is done by the argparse module, but semantic
104 validation still needs to be done. This method is doing it for
105 analyze-build and scan-build commands.
106
107 :param parser: The command line parser object.
108 :param args: Parsed argument object.
109 :param from_build_command: Boolean value tells is the command suppose
110 to run the analyzer against a build command or a compilation db.
111 :return: No return value, but this call might throw when validation
112 fails. """
113
114 if args.help_checkers_verbose:
115 print_checkers(get_checkers(args.clang, args.plugins))
116 parser.exit(status=0)
117 elif args.help_checkers:
118 print_active_checkers(get_checkers(args.clang, args.plugins))
119 parser.exit(status=0)
120 elif from_build_command and not args.build:
121 parser.error(message='missing build command')
122 elif not from_build_command and not os.path.exists(args.cdb):
123 parser.error(message='compilation database is missing')
124
125
126def create_intercept_parser():
127 """ Creates a parser for command-line arguments to 'intercept'. """
128
129 parser = create_default_parser()
130 parser_add_cdb(parser)
131
132 parser_add_prefer_wrapper(parser)
133 parser_add_compilers(parser)
134
135 advanced = parser.add_argument_group('advanced options')
136 group = advanced.add_mutually_exclusive_group()
137 group.add_argument(
138 '--append',
139 action='store_true',
140 help="""Extend existing compilation database with new entries.
141 Duplicate entries are detected and not present in the final output.
142 The output is not continuously updated, it's done when the build
143 command finished. """)
144
145 parser.add_argument(
146 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
147 return parser
148
149
150def create_analyze_parser(from_build_command):
151 """ Creates a parser for command-line arguments to 'analyze'. """
152
153 parser = create_default_parser()
154
155 if from_build_command:
156 parser_add_prefer_wrapper(parser)
157 parser_add_compilers(parser)
158
159 parser.add_argument(
160 '--intercept-first',
161 action='store_true',
162 help="""Run the build commands first, intercept compiler
163 calls and then run the static analyzer afterwards.
164 Generally speaking it has better coverage on build commands.
165 With '--override-compiler' it use compiler wrapper, but does
166 not run the analyzer till the build is finished.""")
167 else:
168 parser_add_cdb(parser)
169
170 parser.add_argument(
171 '--status-bugs',
172 action='store_true',
173 help="""The exit status of '%(prog)s' is the same as the executed
174 build command. This option ignores the build exit status and sets to
175 be non zero if it found potential bugs or zero otherwise.""")
176 parser.add_argument(
177 '--exclude',
178 metavar='<directory>',
179 dest='excludes',
180 action='append',
181 default=[],
182 help="""Do not run static analyzer against files found in this
183 directory. (You can specify this option multiple times.)
184 Could be useful when project contains 3rd party libraries.""")
185
186 output = parser.add_argument_group('output control options')
187 output.add_argument(
188 '--output',
189 '-o',
190 metavar='<path>',
Laszlo Nagy0d9be632017-03-20 09:03:24 +0000191 default=tempfile.gettempdir(),
Laszlo Nagy908ed4c2017-03-08 21:22:32 +0000192 help="""Specifies the output directory for analyzer reports.
193 Subdirectory will be created if default directory is targeted.""")
194 output.add_argument(
195 '--keep-empty',
196 action='store_true',
197 help="""Don't remove the build results directory even if no issues
198 were reported.""")
199 output.add_argument(
200 '--html-title',
201 metavar='<title>',
202 help="""Specify the title used on generated HTML pages.
203 If not specified, a default title will be used.""")
204 format_group = output.add_mutually_exclusive_group()
205 format_group.add_argument(
206 '--plist',
207 '-plist',
208 dest='output_format',
209 const='plist',
210 default='html',
211 action='store_const',
212 help="""Cause the results as a set of .plist files.""")
213 format_group.add_argument(
214 '--plist-html',
215 '-plist-html',
216 dest='output_format',
217 const='plist-html',
218 default='html',
219 action='store_const',
220 help="""Cause the results as a set of .html and .plist files.""")
221 # TODO: implement '-view '
222
223 advanced = parser.add_argument_group('advanced options')
224 advanced.add_argument(
225 '--use-analyzer',
226 metavar='<path>',
227 dest='clang',
228 default='clang',
229 help="""'%(prog)s' uses the 'clang' executable relative to itself for
230 static analysis. One can override this behavior with this option by
231 using the 'clang' packaged with Xcode (on OS X) or from the PATH.""")
232 advanced.add_argument(
233 '--no-failure-reports',
234 '-no-failure-reports',
235 dest='output_failures',
236 action='store_false',
237 help="""Do not create a 'failures' subdirectory that includes analyzer
238 crash reports and preprocessed source files.""")
239 parser.add_argument(
240 '--analyze-headers',
241 action='store_true',
242 help="""Also analyze functions in #included files. By default, such
243 functions are skipped unless they are called by functions within the
244 main source file.""")
245 advanced.add_argument(
246 '--stats',
247 '-stats',
248 action='store_true',
249 help="""Generates visitation statistics for the project.""")
250 advanced.add_argument(
251 '--internal-stats',
252 action='store_true',
253 help="""Generate internal analyzer statistics.""")
254 advanced.add_argument(
255 '--maxloop',
256 '-maxloop',
257 metavar='<loop count>',
258 type=int,
259 help="""Specifiy the number of times a block can be visited before
260 giving up. Increase for more comprehensive coverage at a cost of
261 speed.""")
262 advanced.add_argument(
263 '--store',
264 '-store',
265 metavar='<model>',
266 dest='store_model',
267 choices=['region', 'basic'],
268 help="""Specify the store model used by the analyzer. 'region'
269 specifies a field- sensitive store model. 'basic' which is far less
270 precise but can more quickly analyze code. 'basic' was the default
271 store model for checker-0.221 and earlier.""")
272 advanced.add_argument(
273 '--constraints',
274 '-constraints',
275 metavar='<model>',
276 dest='constraints_model',
277 choices=['range', 'basic'],
278 help="""Specify the constraint engine used by the analyzer. Specifying
279 'basic' uses a simpler, less powerful constraint model used by
280 checker-0.160 and earlier.""")
281 advanced.add_argument(
282 '--analyzer-config',
283 '-analyzer-config',
284 metavar='<options>',
285 help="""Provide options to pass through to the analyzer's
286 -analyzer-config flag. Several options are separated with comma:
287 'key1=val1,key2=val2'
288
289 Available options:
290 stable-report-filename=true or false (default)
291
292 Switch the page naming to:
293 report-<filename>-<function/method name>-<id>.html
294 instead of report-XXXXXX.html""")
295 advanced.add_argument(
296 '--force-analyze-debug-code',
297 dest='force_debug',
298 action='store_true',
299 help="""Tells analyzer to enable assertions in code even if they were
300 disabled during compilation, enabling more precise results.""")
301
302 plugins = parser.add_argument_group('checker options')
303 plugins.add_argument(
304 '--load-plugin',
305 '-load-plugin',
306 metavar='<plugin library>',
307 dest='plugins',
308 action='append',
309 help="""Loading external checkers using the clang plugin interface.""")
310 plugins.add_argument(
311 '--enable-checker',
312 '-enable-checker',
313 metavar='<checker name>',
314 action=AppendCommaSeparated,
315 help="""Enable specific checker.""")
316 plugins.add_argument(
317 '--disable-checker',
318 '-disable-checker',
319 metavar='<checker name>',
320 action=AppendCommaSeparated,
321 help="""Disable specific checker.""")
322 plugins.add_argument(
323 '--help-checkers',
324 action='store_true',
325 help="""A default group of checkers is run unless explicitly disabled.
326 Exactly which checkers constitute the default group is a function of
327 the operating system in use. These can be printed with this flag.""")
328 plugins.add_argument(
329 '--help-checkers-verbose',
330 action='store_true',
331 help="""Print all available checkers and mark the enabled ones.""")
332
333 if from_build_command:
334 parser.add_argument(
335 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
336 return parser
337
338
339def create_default_parser():
340 """ Creates command line parser for all build wrapper commands. """
341
342 parser = argparse.ArgumentParser(
343 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
344
345 parser.add_argument(
346 '--verbose',
347 '-v',
348 action='count',
349 default=0,
350 help="""Enable verbose output from '%(prog)s'. A second, third and
351 fourth flags increases verbosity.""")
352 return parser
353
354
355def parser_add_cdb(parser):
356 parser.add_argument(
357 '--cdb',
358 metavar='<file>',
359 default="compile_commands.json",
360 help="""The JSON compilation database.""")
361
362
363def parser_add_prefer_wrapper(parser):
364 parser.add_argument(
365 '--override-compiler',
366 action='store_true',
367 help="""Always resort to the compiler wrapper even when better
368 intercept methods are available.""")
369
370
371def parser_add_compilers(parser):
372 parser.add_argument(
373 '--use-cc',
374 metavar='<path>',
375 dest='cc',
376 default=os.getenv('CC', 'cc'),
377 help="""When '%(prog)s' analyzes a project by interposing a compiler
378 wrapper, which executes a real compiler for compilation and do other
379 tasks (record the compiler invocation). Because of this interposing,
380 '%(prog)s' does not know what compiler your project normally uses.
381 Instead, it simply overrides the CC environment variable, and guesses
382 your default compiler.
383
384 If you need '%(prog)s' to use a specific compiler for *compilation*
385 then you can use this option to specify a path to that compiler.""")
386 parser.add_argument(
387 '--use-c++',
388 metavar='<path>',
389 dest='cxx',
390 default=os.getenv('CXX', 'c++'),
391 help="""This is the same as "--use-cc" but for C++ code.""")
392
393
394class AppendCommaSeparated(argparse.Action):
395 """ argparse Action class to support multiple comma separated lists. """
396
397 def __call__(self, __parser, namespace, values, __option_string):
398 # getattr(obj, attr, default) does not really returns default but none
399 if getattr(namespace, self.dest, None) is None:
400 setattr(namespace, self.dest, [])
401 # once it's fixed we can use as expected
402 actual = getattr(namespace, self.dest)
403 actual.extend(values.split(','))
404 setattr(namespace, self.dest, actual)
405
406
407def print_active_checkers(checkers):
408 """ Print active checkers to stdout. """
409
410 for name in sorted(name for name, (_, active) in checkers.items()
411 if active):
412 print(name)
413
414
415def print_checkers(checkers):
416 """ Print verbose checker help to stdout. """
417
418 print('')
419 print('available checkers:')
420 print('')
421 for name in sorted(checkers.keys()):
422 description, active = checkers[name]
423 prefix = '+' if active else ' '
424 if len(name) > 30:
425 print(' {0} {1}'.format(prefix, name))
426 print(' ' * 35 + description)
427 else:
428 print(' {0} {1: <30} {2}'.format(prefix, name, description))
429 print('')
430 print('NOTE: "+" indicates that an analysis is enabled by default.')
431 print('')