Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix] Cppcheck unknown C/C++ standard #4359

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading