]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/scan-build-py/libscanbuild/arguments.py
Vendor import of clang trunk r338150:
[FreeBSD/FreeBSD.git] / tools / scan-build-py / libscanbuild / arguments.py
1 # -*- 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
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
10 earlier.)
11
12 It also implements basic validation methods, related to the command.
13 Validations are mostly calling specific help methods, or mangling values.
14 """
15
16 import os
17 import sys
18 import argparse
19 import logging
20 import tempfile
21 from libscanbuild import reconfigure_logging, CtuConfig
22 from libscanbuild.clang import get_checkers, is_ctu_capable
23
24 __all__ = ['parse_args_for_intercept_build', 'parse_args_for_analyze_build',
25            'parse_args_for_scan_build']
26
27
28 def 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
45 def 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
61 def 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
77 def 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     # 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
106
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.
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
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.""")
141
142
143 def 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
167 def 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>',
208         default=tempfile.gettempdir(),
209         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.""")
238     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.""")
247
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="""Specify 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.""")
361     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.""")
406     return parser
407
408
409 def 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
425 def 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
433 def 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
441 def 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
464 class 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
477 def 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
485 def 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('')