mirror of
https://github.com/rizsotto/scan-build.git
synced 2026-09-27 10:01:46 +02:00
The isinstance(item, list) branches could never run: the checker options are argparse 'append' actions, which only produce strings. The hasattr guards were equally moot since the parser always defines both attributes. Flatten with a comprehension instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
294 lines
11 KiB
Python
294 lines
11 KiB
Python
# SPDX-License-Identifier: MIT
|
|
"""This module parses and validates arguments for command-line interfaces.
|
|
|
|
It uses the argparse module to create the command line parser.
|
|
|
|
It also implements basic validation methods, related to the command.
|
|
Validations are mostly calling specific help methods, or mangling values.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
from clanganalyzer.clang import get_checkers
|
|
|
|
# Default values
|
|
DEFAULT_OUTPUT_DIR = tempfile.gettempdir()
|
|
DEFAULT_CDB_FILE = "compile_commands.json"
|
|
|
|
__all__ = ["parse_args"]
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""Parse and validate command-line arguments for clanganalyzer."""
|
|
|
|
parser = create_parser()
|
|
args = parser.parse_args()
|
|
|
|
reconfigure_logging(args.verbose)
|
|
logging.debug("Raw arguments %s", sys.argv)
|
|
|
|
normalize_args(args)
|
|
validate_args(parser, args)
|
|
logging.debug("Parsed arguments: %s", args)
|
|
return args
|
|
|
|
|
|
def reconfigure_logging(verbose_level: int) -> None:
|
|
"""Reconfigure logging level and format based on verbosity.
|
|
|
|
Args:
|
|
verbose_level: Number of `-v` flags received (0 means no change)
|
|
"""
|
|
if verbose_level == 0:
|
|
return
|
|
|
|
root = logging.getLogger()
|
|
|
|
# Calculate log level: more verbose means lower level
|
|
level = max(logging.DEBUG, logging.WARNING - (10 * verbose_level))
|
|
root.setLevel(level)
|
|
|
|
# Choose format based on verbosity
|
|
if verbose_level <= 3:
|
|
fmt_string = "%(name)s: %(levelname)s: %(message)s"
|
|
else:
|
|
fmt_string = "%(name)s: %(levelname)s: %(funcName)s: %(message)s"
|
|
|
|
# Replace existing handlers
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(logging.Formatter(fmt=fmt_string))
|
|
root.handlers = [handler]
|
|
|
|
|
|
def normalize_args(args: argparse.Namespace) -> None:
|
|
"""Normalize parsed arguments for clanganalyzer.
|
|
|
|
:param args: Parsed argument object. (Will be mutated.)"""
|
|
|
|
# make plugins always a list. (it might be None when not specified.)
|
|
if args.plugins is None:
|
|
args.plugins = []
|
|
|
|
# make exclude directory list unique and absolute.
|
|
uniq_excludes = {os.path.abspath(entry) for entry in args.excludes}
|
|
args.excludes = list(uniq_excludes)
|
|
|
|
# flatten comma-separated lists for checker options
|
|
if args.enable_checker:
|
|
args.enable_checker = [checker for item in args.enable_checker for checker in item.split(",")]
|
|
|
|
if args.disable_checker:
|
|
args.disable_checker = [checker for item in args.disable_checker for checker in item.split(",")]
|
|
|
|
|
|
def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
|
|
"""Command line parsing is done by the argparse module, but semantic
|
|
validation still needs to be done. This method is doing it for
|
|
clanganalyzer.
|
|
|
|
:param parser: The command line parser object.
|
|
:param args: Parsed argument object.
|
|
:return: No return value, but this call might throw when validation
|
|
fails."""
|
|
|
|
if args.help_checkers_verbose:
|
|
print_checkers(get_checkers(args.clang, args.plugins))
|
|
parser.exit(status=0)
|
|
elif args.help_checkers:
|
|
print_active_checkers(get_checkers(args.clang, args.plugins))
|
|
parser.exit(status=0)
|
|
elif not os.path.exists(args.cdb):
|
|
parser.error(message="compilation database is missing")
|
|
|
|
|
|
def create_parser() -> argparse.ArgumentParser:
|
|
"""Creates a parser for command-line arguments."""
|
|
|
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
|
|
_ = parser.add_argument(
|
|
"--verbose",
|
|
"-v",
|
|
action="count",
|
|
default=0,
|
|
help="Enable verbose output from clanganalyzer. A second, third and fourth flags increases verbosity.",
|
|
)
|
|
_ = parser.add_argument("--cdb", metavar="<file>", default=DEFAULT_CDB_FILE, help="The JSON compilation database.")
|
|
|
|
_ = parser.add_argument(
|
|
"--status-bugs",
|
|
action="store_true",
|
|
help="The exit status of clanganalyzer is non-zero if potential bugs are found, zero otherwise.",
|
|
)
|
|
_ = parser.add_argument(
|
|
"--exclude",
|
|
metavar="<directory>",
|
|
dest="excludes",
|
|
action="append",
|
|
default=[],
|
|
help="Do not run static analyzer against files found in this directory. (You can specify this option multiple times.) Could be useful when project contains 3rd party libraries.",
|
|
)
|
|
|
|
output = parser.add_argument_group("output control options")
|
|
_ = output.add_argument(
|
|
"--output",
|
|
"-o",
|
|
metavar="<path>",
|
|
default=DEFAULT_OUTPUT_DIR,
|
|
help="Specifies the output directory for analyzer reports. A time-stamped subdirectory will be created inside it.",
|
|
)
|
|
_ = output.add_argument(
|
|
"--keep-empty",
|
|
action="store_true",
|
|
help="Don't remove the build results directory even if no issues were reported.",
|
|
)
|
|
_ = output.add_argument(
|
|
"--html-title",
|
|
metavar="<title>",
|
|
help="Specify the title used on generated HTML pages. If not specified, a default title will be used.",
|
|
)
|
|
format_group = output.add_mutually_exclusive_group()
|
|
_ = format_group.add_argument(
|
|
"--plist",
|
|
dest="output_format",
|
|
const="plist",
|
|
default="html",
|
|
action="store_const",
|
|
help="Cause the results as a set of .plist files.",
|
|
)
|
|
_ = format_group.add_argument(
|
|
"--plist-html",
|
|
dest="output_format",
|
|
const="plist-html",
|
|
default="html",
|
|
action="store_const",
|
|
help="Cause the results as a set of .html and .plist files.",
|
|
)
|
|
_ = format_group.add_argument(
|
|
"--plist-multi-file",
|
|
dest="output_format",
|
|
const="plist-multi-file",
|
|
default="html",
|
|
action="store_const",
|
|
help="Cause the results as a set of .plist files with extra information on related files.",
|
|
)
|
|
advanced = parser.add_argument_group("advanced options")
|
|
_ = advanced.add_argument(
|
|
"--use-analyzer",
|
|
metavar="<path>",
|
|
dest="clang",
|
|
default="clang",
|
|
help="Use the given clang executable for the static analysis. By default the 'clang' executable found in the PATH is used.",
|
|
)
|
|
_ = advanced.add_argument(
|
|
"--analyzer-target",
|
|
dest="analyzer_target",
|
|
metavar="<target triple name for analysis>",
|
|
help="This provides target triple information to clang static analyzer. It only changes the target for analysis but doesn't change the target of a real compiler.",
|
|
)
|
|
_ = advanced.add_argument(
|
|
"--no-failure-reports",
|
|
dest="output_failures",
|
|
action="store_false",
|
|
help="Do not create a 'failures' subdirectory that includes analyzer crash reports and preprocessed source files.",
|
|
)
|
|
_ = parser.add_argument(
|
|
"--analyze-headers",
|
|
action="store_true",
|
|
help="Also analyze functions in #included files. By default, such functions are skipped unless they are called by functions within the main source file.",
|
|
)
|
|
_ = advanced.add_argument("--stats", action="store_true", help="Generates visitation statistics for the project.")
|
|
_ = advanced.add_argument("--internal-stats", action="store_true", help="Generate internal analyzer statistics.")
|
|
_ = advanced.add_argument(
|
|
"--maxloop",
|
|
metavar="<loop count>",
|
|
type=int,
|
|
help="Specify the number of times a block can be visited before giving up. Increase for more comprehensive coverage at a cost of speed.",
|
|
)
|
|
_ = advanced.add_argument(
|
|
"--store",
|
|
metavar="<model>",
|
|
dest="store_model",
|
|
choices=["region", "basic"],
|
|
help="Specify the store model used by the analyzer. 'region' specifies a field-sensitive store model. 'basic' which is far less precise but can more quickly analyze code. 'basic' was the default store model for checker-0.221 and earlier.",
|
|
)
|
|
_ = advanced.add_argument(
|
|
"--constraints",
|
|
metavar="<model>",
|
|
dest="constraints_model",
|
|
choices=["range", "basic"],
|
|
help="Specify the constraint engine used by the analyzer. Specifying 'basic' uses a simpler, less powerful constraint model used by checker-0.160 and earlier.",
|
|
)
|
|
_ = advanced.add_argument(
|
|
"--analyzer-config",
|
|
metavar="<options>",
|
|
help="Provide options to pass through to the analyzer's -analyzer-config flag. Several options are separated with comma: 'key1=val1,key2=val2'. Available options: stable-report-filename=true or false (default). Switch the page naming to: report-<filename>-<function/method name>-<id>.html instead of report-XXXXXX.html",
|
|
)
|
|
_ = advanced.add_argument(
|
|
"--force-analyze-debug-code",
|
|
dest="force_debug",
|
|
action="store_true",
|
|
help="Tells analyzer to enable assertions in code even if they were disabled during compilation, enabling more precise results.",
|
|
)
|
|
|
|
plugins = parser.add_argument_group("checker options")
|
|
_ = plugins.add_argument(
|
|
"--load-plugin",
|
|
metavar="<plugin library>",
|
|
dest="plugins",
|
|
action="append",
|
|
help="Loading external checkers using the clang plugin interface.",
|
|
)
|
|
_ = plugins.add_argument(
|
|
"--enable-checker",
|
|
metavar="<checker name>",
|
|
action="append",
|
|
help="Enable specific checker. Supports comma-separated lists.",
|
|
)
|
|
_ = plugins.add_argument(
|
|
"--disable-checker",
|
|
metavar="<checker name>",
|
|
action="append",
|
|
help="Disable specific checker. Supports comma-separated lists.",
|
|
)
|
|
_ = plugins.add_argument(
|
|
"--help-checkers",
|
|
action="store_true",
|
|
help="A default group of checkers is run unless explicitly disabled. Exactly which checkers constitute the default group is a function of the operating system in use. These can be printed with this flag.",
|
|
)
|
|
_ = plugins.add_argument(
|
|
"--help-checkers-verbose", action="store_true", help="Print all available checkers and mark the enabled ones."
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def print_active_checkers(checkers: dict[str, tuple[str, bool]]) -> None:
|
|
"""Print active checkers to stdout."""
|
|
|
|
for name in sorted(name for name, (_, active) in checkers.items() if active):
|
|
print(name)
|
|
|
|
|
|
def print_checkers(checkers: dict[str, tuple[str, bool]]) -> None:
|
|
"""Print verbose checker help to stdout."""
|
|
|
|
print("")
|
|
print("available checkers:")
|
|
print("")
|
|
for name in sorted(checkers.keys()):
|
|
description, active = checkers[name]
|
|
prefix = "+" if active else " "
|
|
if len(name) > 30:
|
|
print(f" {prefix} {name}")
|
|
print(" " * 35 + description)
|
|
else:
|
|
print(f" {prefix} {name: <30} {description}")
|
|
print("")
|
|
print('NOTE: "+" indicates that an analysis is enabled by default.')
|
|
print("")
|