Skip to content

Commit

Permalink
Merge pull request #4359 from bruntib/cppcheck_standard_versions
Browse files Browse the repository at this point in the history
[fix] Cppcheck unknown C/C++ standard
  • Loading branch information
dkrupp authored Nov 25, 2024
2 parents 005036a + ed42e73 commit b0555b9
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 3 deletions.
42 changes: 41 additions & 1 deletion analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import xml.etree.ElementTree as ET

from codechecker_common.logger import get_logger
from codechecker_common import util

from codechecker_analyzer import analyzer_context, env
from codechecker_analyzer.env import get_binary_in_path
Expand Down Expand Up @@ -138,6 +139,34 @@ def parse_analyzer_config(self):
# * -std c99
# * -stdlib=libc++
std_regex = re.compile("-(-std$|-?std=.*)")

# Mapping is needed, because, if a standard version not known by
# cppcheck is used, then it will assume the latest available version
# before cppcheck-2.15 or fail the analysis from cppcheck-2.15.
# https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html#index-std-1
standard_mapping = {
"c90": "c89",
"c18": "c17",
"iso9899:2017": "c17",
"iso9899:2018": "c17",
"iso9899:1990": "c89",
"iso9899:199409": "c89", # Good enough
"c9x": "c99",
"iso9899:1999": "c99",
"iso9899:199x": "c99",
"c1x": "c11",
"iso9899:2011": "c11",
"c2x": "c23",
"iso9899:2024": "c23",
"c++98": "c++03",
"c++0x": "c++11",
"c++1y": "c++14",
"c++1z": "c++17",
"c++2a": "c++20",
"c++2b": "c++23",
"c++2c": "c++26"
}

for i, analyzer_option in enumerate(self.buildaction.analyzer_options):
if interesting_option.match(analyzer_option):
params.extend([analyzer_option])
Expand All @@ -161,6 +190,7 @@ def parse_analyzer_config(self):
else:
standard = self.buildaction.analyzer_options[i+1]
standard = standard.lower().replace("gnu", "c")
standard = standard_mapping.get(standard, standard)
params.extend(["--std=" + standard])
return params

Expand Down Expand Up @@ -194,7 +224,17 @@ def construct_analyzer_cmd(self, result_handler):
analyzer_cmd.extend(config.analyzer_extra_arguments)

# Pass whitelisted parameters
analyzer_cmd.extend(self.parse_analyzer_config())
params = self.parse_analyzer_config()

def is_std(arg):
return arg.startswith("--std=")

if util.index_of(config.analyzer_extra_arguments, is_std) >= 0:
std_idx = util.index_of(params, is_std)
if std_idx >= 0:
del params[std_idx]

analyzer_cmd.extend(params)

# TODO fix this in a follow up patch, because it is failing
# the macos pypy test.
Expand Down
48 changes: 46 additions & 2 deletions analyzer/tests/functional/analyze/test_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import shutil
import subprocess
import shlex
import tempfile
import unittest
import zipfile

Expand Down Expand Up @@ -1083,7 +1084,7 @@ def test_cppcheck_standard(self):
stdout=subprocess.PIPE).stdout.decode()

# Test correct handover.
self.assertTrue("--std=c99" in out)
self.assertIn("--std=c99", out)

# Cppcheck does not support gnu variants of the standards,
# These are transformed into their respective c and c++
Expand All @@ -1104,7 +1105,50 @@ def test_cppcheck_standard(self):
stdout=subprocess.PIPE).stdout.decode()

# Test if the standard is correctly transformed
self.assertTrue("--std=c99" in out)
self.assertIn("--std=c99", out)

build_log = [{"directory": self.test_workspace,
"command": "gcc -c -std=iso9899:2017 " + source_file,
"file": source_file
}]

with open(build_json, 'w',
encoding="utf-8", errors="ignore") as outfile:
json.dump(build_log, outfile)

out = subprocess.run(analyze_cmd,
cwd=self.test_dir,
# env=self.env,
check=False,
stdout=subprocess.PIPE).stdout.decode()

self.assertNotIn("iso9899:2017", out)
self.assertIn("--std=c17", out)

# Test if standard version in --cppcheckargs is stronger.
with tempfile.NamedTemporaryFile(mode='w',
encoding='utf-8') as cppcheck_args:
with open(build_json, 'w',
encoding="utf-8", errors="ignore") as outfile:
build_log = [{
"directory": self.test_workspace,
"command": "gcc -c -std=c++0x " + source_file,
"file": source_file}]
json.dump(build_log, outfile)

cppcheck_args.write("--std=c++11")
cppcheck_args.close()

analyze_cmd.extend(['--cppcheckargs', cppcheck_args.name])

out = subprocess.run(analyze_cmd,
cwd=self.test_dir,
# env=self.env,
check=False,
stdout=subprocess.PIPE).stdout.decode()

self.assertIn("--std=c++11", out)
self.assertNotIn("--std=c++0x", out)

def test_makefile_generation(self):
""" Test makefile generation. """
Expand Down
11 changes: 11 additions & 0 deletions codechecker_common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,14 @@ def path_for_fake_root(full_path: str, root_path: str = '/') -> str:
def strtobool(value: str) -> bool:
"""Parse a string value to a boolean."""
return value.lower() in ('y', 'yes', 't', 'true', 'on', '1')


def index_of(iterable, lambda_func) -> int:
"""Return the index of the first element in iterable for which
lambda_func returns True.
"""
for i, item in enumerate(iterable):
if lambda_func(item):
return i

return -1

0 comments on commit b0555b9

Please sign in to comment.