Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / clang / tools / scan-build-py / lib / libscanbuild / arguments.py
blobe9794e795977889b8356cf371a3e987cc8fe2e54
1 # -*- coding: utf-8 -*-
2 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3 # See https://llvm.org/LICENSE.txt for license information.
4 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5 """ This module parses and validates arguments for command-line interfaces.
7 It uses argparse module to create the command line parser. (This library is
8 in the standard python library since 3.2 and backported to 2.7, but not
9 earlier.)
11 It also implements basic validation methods, related to the command.
12 Validations are mostly calling specific help methods, or mangling values.
13 """
14 from __future__ import absolute_import, division, print_function
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
24 __all__ = [
25 "parse_args_for_intercept_build",
26 "parse_args_for_analyze_build",
27 "parse_args_for_scan_build",
31 def parse_args_for_intercept_build():
32 """Parse and validate command-line arguments for intercept-build."""
34 parser = create_intercept_parser()
35 args = parser.parse_args()
37 reconfigure_logging(args.verbose)
38 logging.debug("Raw arguments %s", sys.argv)
40 # short validation logic
41 if not args.build:
42 parser.error(message="missing build command")
44 logging.debug("Parsed arguments: %s", args)
45 return args
48 def parse_args_for_analyze_build():
49 """Parse and validate command-line arguments for analyze-build."""
51 from_build_command = False
52 parser = create_analyze_parser(from_build_command)
53 args = parser.parse_args()
55 reconfigure_logging(args.verbose)
56 logging.debug("Raw arguments %s", sys.argv)
58 normalize_args_for_analyze(args, from_build_command)
59 validate_args_for_analyze(parser, args, from_build_command)
60 logging.debug("Parsed arguments: %s", args)
61 return args
64 def parse_args_for_scan_build():
65 """Parse and validate command-line arguments for scan-build."""
67 from_build_command = True
68 parser = create_analyze_parser(from_build_command)
69 args = parser.parse_args()
71 reconfigure_logging(args.verbose)
72 logging.debug("Raw arguments %s", sys.argv)
74 normalize_args_for_analyze(args, from_build_command)
75 validate_args_for_analyze(parser, args, from_build_command)
76 logging.debug("Parsed arguments: %s", args)
77 return args
80 def normalize_args_for_analyze(args, from_build_command):
81 """Normalize parsed arguments for analyze-build and scan-build.
83 :param args: Parsed argument object. (Will be mutated.)
84 :param from_build_command: Boolean value tells is the command suppose
85 to run the analyzer against a build command or a compilation db."""
87 # make plugins always a list. (it might be None when not specified.)
88 if args.plugins is None:
89 args.plugins = []
91 # make exclude directory list unique and absolute.
92 uniq_excludes = set(os.path.abspath(entry) for entry in args.excludes)
93 args.excludes = list(uniq_excludes)
95 # because shared codes for all tools, some common used methods are
96 # expecting some argument to be present. so, instead of query the args
97 # object about the presence of the flag, we fake it here. to make those
98 # methods more readable. (it's an arguable choice, took it only for those
99 # which have good default value.)
100 if from_build_command:
101 # add cdb parameter invisibly to make report module working.
102 args.cdb = "compile_commands.json"
104 # Make ctu_dir an abspath as it is needed inside clang
105 if (
106 not from_build_command
107 and hasattr(args, "ctu_phases")
108 and hasattr(args.ctu_phases, "dir")
110 args.ctu_dir = os.path.abspath(args.ctu_dir)
113 def validate_args_for_analyze(parser, args, from_build_command):
114 """Command line parsing is done by the argparse module, but semantic
115 validation still needs to be done. This method is doing it for
116 analyze-build and scan-build commands.
118 :param parser: The command line parser object.
119 :param args: Parsed argument object.
120 :param from_build_command: Boolean value tells is the command suppose
121 to run the analyzer against a build command or a compilation db.
122 :return: No return value, but this call might throw when validation
123 fails."""
125 if args.help_checkers_verbose:
126 print_checkers(get_checkers(args.clang, args.plugins))
127 parser.exit(status=0)
128 elif args.help_checkers:
129 print_active_checkers(get_checkers(args.clang, args.plugins))
130 parser.exit(status=0)
131 elif from_build_command and not args.build:
132 parser.error(message="missing build command")
133 elif not from_build_command and not os.path.exists(args.cdb):
134 parser.error(message="compilation database is missing")
136 # If the user wants CTU mode
137 if (
138 not from_build_command
139 and hasattr(args, "ctu_phases")
140 and hasattr(args.ctu_phases, "dir")
142 # If CTU analyze_only, the input directory should exist
143 if (
144 args.ctu_phases.analyze
145 and not args.ctu_phases.collect
146 and not os.path.exists(args.ctu_dir)
148 parser.error(message="missing CTU directory")
149 # Check CTU capability via checking clang-extdef-mapping
150 if not is_ctu_capable(args.extdef_map_cmd):
151 parser.error(
152 message="""This version of clang does not support CTU
153 functionality or clang-extdef-mapping command not found."""
157 def create_intercept_parser():
158 """Creates a parser for command-line arguments to 'intercept'."""
160 parser = create_default_parser()
161 parser_add_cdb(parser)
163 parser_add_prefer_wrapper(parser)
164 parser_add_compilers(parser)
166 advanced = parser.add_argument_group("advanced options")
167 group = advanced.add_mutually_exclusive_group()
168 group.add_argument(
169 "--append",
170 action="store_true",
171 help="""Extend existing compilation database with new entries.
172 Duplicate entries are detected and not present in the final output.
173 The output is not continuously updated, it's done when the build
174 command finished. """,
177 parser.add_argument(
178 dest="build", nargs=argparse.REMAINDER, help="""Command to run."""
180 return parser
183 def create_analyze_parser(from_build_command):
184 """Creates a parser for command-line arguments to 'analyze'."""
186 parser = create_default_parser()
188 if from_build_command:
189 parser_add_prefer_wrapper(parser)
190 parser_add_compilers(parser)
192 parser.add_argument(
193 "--intercept-first",
194 action="store_true",
195 help="""Run the build commands first, intercept compiler
196 calls and then run the static analyzer afterwards.
197 Generally speaking it has better coverage on build commands.
198 With '--override-compiler' it use compiler wrapper, but does
199 not run the analyzer till the build is finished.""",
201 else:
202 parser_add_cdb(parser)
204 parser.add_argument(
205 "--status-bugs",
206 action="store_true",
207 help="""The exit status of '%(prog)s' is the same as the executed
208 build command. This option ignores the build exit status and sets to
209 be non zero if it found potential bugs or zero otherwise.""",
211 parser.add_argument(
212 "--exclude",
213 metavar="<directory>",
214 dest="excludes",
215 action="append",
216 default=[],
217 help="""Do not run static analyzer against files found in this
218 directory. (You can specify this option multiple times.)
219 Could be useful when project contains 3rd party libraries.""",
222 output = parser.add_argument_group("output control options")
223 output.add_argument(
224 "--output",
225 "-o",
226 metavar="<path>",
227 default=tempfile.gettempdir(),
228 help="""Specifies the output directory for analyzer reports.
229 Subdirectory will be created if default directory is targeted.""",
231 output.add_argument(
232 "--keep-empty",
233 action="store_true",
234 help="""Don't remove the build results directory even if no issues
235 were reported.""",
237 output.add_argument(
238 "--html-title",
239 metavar="<title>",
240 help="""Specify the title used on generated HTML pages.
241 If not specified, a default title will be used.""",
243 format_group = output.add_mutually_exclusive_group()
244 format_group.add_argument(
245 "--plist",
246 "-plist",
247 dest="output_format",
248 const="plist",
249 default="html",
250 action="store_const",
251 help="""Cause the results as a set of .plist files.""",
253 format_group.add_argument(
254 "--plist-html",
255 "-plist-html",
256 dest="output_format",
257 const="plist-html",
258 default="html",
259 action="store_const",
260 help="""Cause the results as a set of .html and .plist files.""",
262 format_group.add_argument(
263 "--plist-multi-file",
264 "-plist-multi-file",
265 dest="output_format",
266 const="plist-multi-file",
267 default="html",
268 action="store_const",
269 help="""Cause the results as a set of .plist files with extra
270 information on related files.""",
272 format_group.add_argument(
273 "--sarif",
274 "-sarif",
275 dest="output_format",
276 const="sarif",
277 default="html",
278 action="store_const",
279 help="""Cause the results as a result.sarif file.""",
281 format_group.add_argument(
282 "--sarif-html",
283 "-sarif-html",
284 dest="output_format",
285 const="sarif-html",
286 default="html",
287 action="store_const",
288 help="""Cause the results as a result.sarif file and .html files.""",
291 advanced = parser.add_argument_group("advanced options")
292 advanced.add_argument(
293 "--use-analyzer",
294 metavar="<path>",
295 dest="clang",
296 default="clang",
297 help="""'%(prog)s' uses the 'clang' executable relative to itself for
298 static analysis. One can override this behavior with this option by
299 using the 'clang' packaged with Xcode (on OS X) or from the PATH.""",
301 advanced.add_argument(
302 "--no-failure-reports",
303 "-no-failure-reports",
304 dest="output_failures",
305 action="store_false",
306 help="""Do not create a 'failures' subdirectory that includes analyzer
307 crash reports and preprocessed source files.""",
309 parser.add_argument(
310 "--analyze-headers",
311 action="store_true",
312 help="""Also analyze functions in #included files. By default, such
313 functions are skipped unless they are called by functions within the
314 main source file.""",
316 advanced.add_argument(
317 "--stats",
318 "-stats",
319 action="store_true",
320 help="""Generates visitation statistics for the project.""",
322 advanced.add_argument(
323 "--internal-stats",
324 action="store_true",
325 help="""Generate internal analyzer statistics.""",
327 advanced.add_argument(
328 "--maxloop",
329 "-maxloop",
330 metavar="<loop count>",
331 type=int,
332 help="""Specify the number of times a block can be visited before
333 giving up. Increase for more comprehensive coverage at a cost of
334 speed.""",
336 advanced.add_argument(
337 "--store",
338 "-store",
339 metavar="<model>",
340 dest="store_model",
341 choices=["region", "basic"],
342 help="""Specify the store model used by the analyzer. 'region'
343 specifies a field- sensitive store model. 'basic' which is far less
344 precise but can more quickly analyze code. 'basic' was the default
345 store model for checker-0.221 and earlier.""",
347 advanced.add_argument(
348 "--constraints",
349 "-constraints",
350 metavar="<model>",
351 dest="constraints_model",
352 choices=["range", "basic"],
353 help="""Specify the constraint engine used by the analyzer. Specifying
354 'basic' uses a simpler, less powerful constraint model used by
355 checker-0.160 and earlier.""",
357 advanced.add_argument(
358 "--analyzer-config",
359 "-analyzer-config",
360 metavar="<options>",
361 help="""Provide options to pass through to the analyzer's
362 -analyzer-config flag. Several options are separated with comma:
363 'key1=val1,key2=val2'
365 Available options:
366 stable-report-filename=true or false (default)
368 Switch the page naming to:
369 report-<filename>-<function/method name>-<id>.html
370 instead of report-XXXXXX.html""",
372 advanced.add_argument(
373 "--force-analyze-debug-code",
374 dest="force_debug",
375 action="store_true",
376 help="""Tells analyzer to enable assertions in code even if they were
377 disabled during compilation, enabling more precise results.""",
380 plugins = parser.add_argument_group("checker options")
381 plugins.add_argument(
382 "--load-plugin",
383 "-load-plugin",
384 metavar="<plugin library>",
385 dest="plugins",
386 action="append",
387 help="""Loading external checkers using the clang plugin interface.""",
389 plugins.add_argument(
390 "--enable-checker",
391 "-enable-checker",
392 metavar="<checker name>",
393 action=AppendCommaSeparated,
394 help="""Enable specific checker.""",
396 plugins.add_argument(
397 "--disable-checker",
398 "-disable-checker",
399 metavar="<checker name>",
400 action=AppendCommaSeparated,
401 help="""Disable specific checker.""",
403 plugins.add_argument(
404 "--help-checkers",
405 action="store_true",
406 help="""A default group of checkers is run unless explicitly disabled.
407 Exactly which checkers constitute the default group is a function of
408 the operating system in use. These can be printed with this flag.""",
410 plugins.add_argument(
411 "--help-checkers-verbose",
412 action="store_true",
413 help="""Print all available checkers and mark the enabled ones.""",
416 if from_build_command:
417 parser.add_argument(
418 dest="build", nargs=argparse.REMAINDER, help="""Command to run."""
420 else:
421 ctu = parser.add_argument_group("cross translation unit analysis")
422 ctu_mutex_group = ctu.add_mutually_exclusive_group()
423 ctu_mutex_group.add_argument(
424 "--ctu",
425 action="store_const",
426 const=CtuConfig(collect=True, analyze=True, dir="", extdef_map_cmd=""),
427 dest="ctu_phases",
428 help="""Perform cross translation unit (ctu) analysis (both collect
429 and analyze phases) using default <ctu-dir> for temporary output.
430 At the end of the analysis, the temporary directory is removed.""",
432 ctu.add_argument(
433 "--ctu-dir",
434 metavar="<ctu-dir>",
435 dest="ctu_dir",
436 default="ctu-dir",
437 help="""Defines the temporary directory used between ctu
438 phases.""",
440 ctu_mutex_group.add_argument(
441 "--ctu-collect-only",
442 action="store_const",
443 const=CtuConfig(collect=True, analyze=False, dir="", extdef_map_cmd=""),
444 dest="ctu_phases",
445 help="""Perform only the collect phase of ctu.
446 Keep <ctu-dir> for further use.""",
448 ctu_mutex_group.add_argument(
449 "--ctu-analyze-only",
450 action="store_const",
451 const=CtuConfig(collect=False, analyze=True, dir="", extdef_map_cmd=""),
452 dest="ctu_phases",
453 help="""Perform only the analyze phase of ctu. <ctu-dir> should be
454 present and will not be removed after analysis.""",
456 ctu.add_argument(
457 "--use-extdef-map-cmd",
458 metavar="<path>",
459 dest="extdef_map_cmd",
460 default="clang-extdef-mapping",
461 help="""'%(prog)s' uses the 'clang-extdef-mapping' executable
462 relative to itself for generating external definition maps for
463 static analysis. One can override this behavior with this option
464 by using the 'clang-extdef-mapping' packaged with Xcode (on OS X)
465 or from the PATH.""",
467 return parser
470 def create_default_parser():
471 """Creates command line parser for all build wrapper commands."""
473 parser = argparse.ArgumentParser(
474 formatter_class=argparse.ArgumentDefaultsHelpFormatter
477 parser.add_argument(
478 "--verbose",
479 "-v",
480 action="count",
481 default=0,
482 help="""Enable verbose output from '%(prog)s'. A second, third and
483 fourth flags increases verbosity.""",
485 return parser
488 def parser_add_cdb(parser):
489 parser.add_argument(
490 "--cdb",
491 metavar="<file>",
492 default="compile_commands.json",
493 help="""The JSON compilation database.""",
497 def parser_add_prefer_wrapper(parser):
498 parser.add_argument(
499 "--override-compiler",
500 action="store_true",
501 help="""Always resort to the compiler wrapper even when better
502 intercept methods are available.""",
506 def parser_add_compilers(parser):
507 parser.add_argument(
508 "--use-cc",
509 metavar="<path>",
510 dest="cc",
511 default=os.getenv("CC", "cc"),
512 help="""When '%(prog)s' analyzes a project by interposing a compiler
513 wrapper, which executes a real compiler for compilation and do other
514 tasks (record the compiler invocation). Because of this interposing,
515 '%(prog)s' does not know what compiler your project normally uses.
516 Instead, it simply overrides the CC environment variable, and guesses
517 your default compiler.
519 If you need '%(prog)s' to use a specific compiler for *compilation*
520 then you can use this option to specify a path to that compiler.""",
522 parser.add_argument(
523 "--use-c++",
524 metavar="<path>",
525 dest="cxx",
526 default=os.getenv("CXX", "c++"),
527 help="""This is the same as "--use-cc" but for C++ code.""",
531 class AppendCommaSeparated(argparse.Action):
532 """argparse Action class to support multiple comma separated lists."""
534 def __call__(self, __parser, namespace, values, __option_string):
535 # getattr(obj, attr, default) does not really returns default but none
536 if getattr(namespace, self.dest, None) is None:
537 setattr(namespace, self.dest, [])
538 # once it's fixed we can use as expected
539 actual = getattr(namespace, self.dest)
540 actual.extend(values.split(","))
541 setattr(namespace, self.dest, actual)
544 def print_active_checkers(checkers):
545 """Print active checkers to stdout."""
547 for name in sorted(name for name, (_, active) in checkers.items() if active):
548 print(name)
551 def print_checkers(checkers):
552 """Print verbose checker help to stdout."""
554 print("")
555 print("available checkers:")
556 print("")
557 for name in sorted(checkers.keys()):
558 description, active = checkers[name]
559 prefix = "+" if active else " "
560 if len(name) > 30:
561 print(" {0} {1}".format(prefix, name))
562 print(" " * 35 + description)
563 else:
564 print(" {0} {1: <30} {2}".format(prefix, name, description))
565 print("")
566 print('NOTE: "+" indicates that an analysis is enabled by default.')
567 print("")