Source code for statick_tool.plugins.tool.cppcheck_tool_plugin

"""Apply cppcheck tool and gather results."""
import argparse
import logging
import os
import re
import subprocess
from typing import List, Match, Optional, Pattern

from statick_tool.issue import Issue
from statick_tool.package import Package
from statick_tool.tool_plugin import ToolPlugin


[docs]class CppcheckToolPlugin(ToolPlugin): """Apply cppcheck tool and gather results.""" # pylint: disable=super-init-not-called def __init__(self) -> None: """Initialize cppcheck extensions.""" self.valid_extensions = [".h", ".hpp", ".c", ".cc", ".cpp", ".cxx"] # pylint: enable=super-init-not-called
[docs] def get_name(self) -> str: """Get name of tool.""" return "cppcheck"
[docs] def gather_args(self, args: argparse.Namespace) -> None: """Gather arguments.""" args.add_argument( "--cppcheck-bin", dest="cppcheck_bin", type=str, help="cppcheck binary path" )
# pylint: disable=too-many-locals, too-many-branches, too-many-return-statements
[docs] def scan(self, package: Package, level: str) -> Optional[List[Issue]]: """Run tool and gather output.""" if ( "make_targets" not in package and "headers" not in package ) or self.plugin_context is None: return [] flags: List[str] = [ "--report-progress", "--verbose", "--inline-suppr", "--language=c++", "--template=[{file}:{line}]: ({severity} {id}) {message}", ] flags += self.get_user_flags(level) user_version = self.plugin_context.config.get_tool_config( self.get_name(), level, "version" ) cppcheck_bin = "cppcheck" if self.plugin_context.args.cppcheck_bin is not None: cppcheck_bin = self.plugin_context.args.cppcheck_bin try: output = subprocess.check_output( [cppcheck_bin, "--version"], stderr=subprocess.STDOUT, universal_newlines=True, ) ver_re = r"(.+) ([0-9]*\.?[0-9]+)" parse: Pattern[str] = re.compile(ver_re) match: Optional[Match[str]] = parse.match(output) if match: ver = float(match.group(2)) # If specific version is not specified just use the installed version. if user_version is not None and ver != float(user_version): logging.warning( "You need version %s of cppcheck, but you have %s. " "See README.md for instuctions on how to install the " "proper version", user_version, match.group(2), ) return None except OSError as ex: logging.warning("Cppcheck not found! (%s)", ex) return None except subprocess.CalledProcessError as ex: output = ex.output logging.warning("Cppcheck failed! Returncode = %d", ex.returncode) logging.warning("%s exception: %s", self.get_name(), ex.output) return None files: List[str] = [] include_dirs: List[str] = [] if "make_targets" in package: for target in package["make_targets"]: files += target["src"] if "include_dirs" in target: for include_dir in target["include_dirs"]: if include_dir not in include_dirs: include_dirs.append(include_dir) if "headers" in package: files += package["headers"] if not files: return [] include_args = [] for include_dir in include_dirs: if package.path in include_dir: include_args.append("-I") include_args.append(include_dir) try: output = subprocess.check_output( [cppcheck_bin] + flags + include_args + files, stderr=subprocess.STDOUT, universal_newlines=True, ) except subprocess.CalledProcessError as ex: output = ex.output logging.warning("cppcheck failed! Returncode = %d", ex.returncode) logging.warning("%s exception: %s", self.get_name(), ex.output) return None logging.debug("%s", output) if self.plugin_context and self.plugin_context.args.output_directory: with open(self.get_name() + ".log", "w", encoding="utf8") as fid: fid.write(output) issues: List[Issue] = self.parse_tool_output(output) return issues
# pylint: enable=too-many-locals, too-many-branches, too-many-return-statements
[docs] @classmethod def check_for_exceptions(cls, match: Match[str]) -> bool: """Manual exceptions.""" # Sometimes you can't fix variableScope in old c code if match.group(1).endswith(".c") and match.group(4) == "variableScope": return True return False
[docs] def parse_tool_output(self, output: str) -> List[Issue]: """Parse tool output and report issues.""" cppcheck_re = r"\[(.+):(\d+)\]:\s\((.+?)\s(.+?)\)\s(.+)" parse: Pattern[str] = re.compile(cppcheck_re) issues: List[Issue] = [] warnings_mapping = self.load_mapping() for line in output.splitlines(): match: Optional[Match[str]] = parse.match(line) if ( match and line[1] != "*" and match.group(3) != "information" and not self.check_for_exceptions(match) ): dummy, extension = os.path.splitext(match.group(1)) if extension in self.valid_extensions: cert_reference = None if match.group(4) in warnings_mapping: cert_reference = warnings_mapping[match.group(4)] issues.append( Issue( match.group(1), match.group(2), self.get_name(), match.group(3) + "/" + match.group(4), "5", match.group(5), cert_reference, ) ) return issues