IAR: Add support for C-STAT static analysis

The IAR platform offers an integrated static analysis tool named
IAR C-STAT.

Closes: #26844
This commit is contained in:
Felipe Torrezan
2025-04-08 17:29:06 +02:00
committed by Brad King
parent a970c5cfdb
commit c7d2a17253
23 changed files with 194 additions and 13 deletions

View File

@@ -318,6 +318,7 @@ Properties on Targets
/prop_tgt/LANG_CPPCHECK
/prop_tgt/LANG_CPPLINT
/prop_tgt/LANG_EXTENSIONS
/prop_tgt/LANG_ICSTAT
/prop_tgt/LANG_INCLUDE_WHAT_YOU_USE
/prop_tgt/LANG_LINKER_LAUNCHER
/prop_tgt/LANG_STANDARD

View File

@@ -489,6 +489,7 @@ Variables that Control the Build
/variable/CMAKE_LANG_COMPILER_LAUNCHER
/variable/CMAKE_LANG_CPPCHECK
/variable/CMAKE_LANG_CPPLINT
/variable/CMAKE_LANG_ICSTAT
/variable/CMAKE_LANG_INCLUDE_WHAT_YOU_USE
/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE
/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED

View File

@@ -6,11 +6,11 @@ SKIP_LINTING
This property allows you to exclude a specific source file
from the linting process. The linting process involves running
tools such as :prop_tgt:`<LANG>_CPPLINT`, :prop_tgt:`<LANG>_CLANG_TIDY`,
:prop_tgt:`<LANG>_CPPCHECK`, and :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`
on the source files, as well as compiling header files as part of
:prop_tgt:`VERIFY_INTERFACE_HEADER_SETS`. By setting ``SKIP_LINTING`` on a
source file, the mentioned linting tools will not be executed for that
particular file.
:prop_tgt:`<LANG>_CPPCHECK`, :prop_tgt:`<LANG>_ICSTAT` and
:prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE` on the source files, as well
as compiling header files as part of :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS`.
By setting ``SKIP_LINTING`` on a source file, the mentioned linting tools
will not be executed for that particular file.
Example
^^^^^^^
@@ -33,8 +33,9 @@ command as shown below:
In the provided code snippet, the ``SKIP_LINTING`` property is set to true
for the ``generatedBindings.cpp`` source file. As a result, when the linting
tools specified by :prop_tgt:`<LANG>_CPPLINT`, :prop_tgt:`<LANG>_CLANG_TIDY`,
:prop_tgt:`<LANG>_CPPCHECK`, or :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`
are executed, they will skip analyzing the ``generatedBindings.cpp`` file.
:prop_tgt:`<LANG>_CPPCHECK`, :prop_tgt:`<LANG>_ICSTAT` or
:prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE` are executed, they will skip analyzing
the ``generatedBindings.cpp`` file.
By using the ``SKIP_LINTING`` property, you can selectively exclude specific
source files from the linting process. This allows you to focus the

View File

@@ -0,0 +1,20 @@
<LANG>_ICSTAT
-------------
.. versionadded:: 4.1
This property is supported only when ``<LANG>`` is ``C`` or ``CXX``.
Specify a :ref:`semicolon-separated list <CMake Language Lists>`
containing a command line for the ``icstat`` static analysis tool.
The :ref:`Makefile Generators` and the :ref:`Ninja Generators` will
run ``icstat`` along with the compiler and report any problems.
The build will fail if the ``icstat`` tool returns non-zero.
This property is initialized by the value of the
:variable:`CMAKE_<LANG>_ICSTAT` variable if it is set when a target is
created. It also supports
:manual:`generator expressions <cmake-generator-expressions(7)>`.
This lint may be suppressed for individual source files by setting
the :prop_sf:`SKIP_LINTING` source file property.

View File

@@ -0,0 +1,8 @@
iar-add-icstat-support
----------------------
* The :variable:`CMAKE_<LANG>_ICSTAT` variable and corresponding
:prop_tgt:`<LANG>_ICSTAT` target property were added to tell
the :ref:`Makefile Generators` and the :ref:`Ninja Generators`
to run the IAR ``icstat`` tool along with the compiler for
``C`` and ``CXX`` languages.

View File

@@ -0,0 +1,8 @@
CMAKE_<LANG>_ICSTAT
-------------------
.. versionadded:: 4.1
Default value for :prop_tgt:`<LANG>_ICSTAT` target property. This variable
is used to initialize the property on each target as it is created. This
is done only when ``<LANG>`` is ``C`` or ``CXX``.

View File

@@ -360,6 +360,7 @@ std::string cmCommonTargetGenerator::GenerateCodeCheckRules(
std::string iwyu;
std::string cpplint;
std::string cppcheck;
std::string icstat;
auto evaluateProp = [&](std::string const& prop) -> std::string {
auto const value = this->GeneratorTarget->GetProperty(prop);
@@ -383,9 +384,12 @@ std::string cmCommonTargetGenerator::GenerateCodeCheckRules(
std::string const cppcheck_prop = cmStrCat(lang, "_CPPCHECK");
cppcheck = evaluateProp(cppcheck_prop);
std::string const icstat_prop = cmStrCat(lang, "_ICSTAT");
icstat = evaluateProp(icstat_prop);
}
if (cmNonempty(iwyu) || cmNonempty(tidy) || cmNonempty(cpplint) ||
cmNonempty(cppcheck)) {
cmNonempty(cppcheck) || cmNonempty(icstat)) {
std::string code_check = cmakeCmd + " -E __run_co_compile";
if (!compilerLauncher.empty()) {
// In __run_co_compile case the launcher command is supplied
@@ -481,6 +485,24 @@ std::string cmCommonTargetGenerator::GenerateCodeCheckRules(
code_check +=
this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(cppcheck);
}
if (cmNonempty(icstat)) {
code_check += " --icstat=";
std::string checksParam{};
std::string dbParam{};
// Set default values for mandatory parameters
std::string checksFile{ "cstat_sel_checks.txt" };
std::string dbFile{ "cstat.db" };
// Populate the command line with C-STAT
// mandatory parameters unless specified
if (icstat.find("--checks=") == std::string::npos) {
checksParam = cmStrCat(";--checks=", checksFile);
}
if (icstat.find("--db=") == std::string::npos) {
dbParam = cmStrCat(";--db=", dbFile);
}
code_check += this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(
cmStrCat(icstat, checksParam, dbParam));
}
if (cmNonempty(tidy) || (cmNonempty(cpplint)) || (cmNonempty(cppcheck))) {
code_check += " --source=";
code_check +=

View File

@@ -463,12 +463,14 @@ TargetProperty const StaticTargetProperties[] = {
{ "C_CLANG_TIDY_EXPORT_FIXES_DIR"_s, IC::CanCompileSources },
{ "C_CPPLINT"_s, IC::CanCompileSources },
{ "C_CPPCHECK"_s, IC::CanCompileSources },
{ "C_ICSTAT"_s, IC::CanCompileSources },
{ "C_INCLUDE_WHAT_YOU_USE"_s, IC::CanCompileSources },
// -- C++
{ "CXX_CLANG_TIDY"_s, IC::CanCompileSources },
{ "CXX_CLANG_TIDY_EXPORT_FIXES_DIR"_s, IC::CanCompileSources },
{ "CXX_CPPLINT"_s, IC::CanCompileSources },
{ "CXX_CPPCHECK"_s, IC::CanCompileSources },
{ "CXX_ICSTAT"_s, IC::CanCompileSources },
{ "CXX_INCLUDE_WHAT_YOU_USE"_s, IC::CanCompileSources },
// -- Objective C
{ "OBJC_CLANG_TIDY"_s, IC::CanCompileSources },
@@ -1783,6 +1785,7 @@ void cmTarget::CopyImportedCxxModulesProperties(cmTarget const* tgt)
"CXX_CLANG_TIDY_EXPORT_FIXES_DIR",
"CXX_CPPLINT",
"CXX_CPPCHECK",
"CXX_ICSTAT",
"CXX_INCLUDE_WHAT_YOU_USE",
// Build graph properties

View File

@@ -513,6 +513,55 @@ int HandleCppCheck(std::string const& runCmd, std::string const& sourceFile,
return ret;
}
int HandleIcstat(std::string const& runCmd, std::string const& sourceFile,
std::vector<std::string> const& orig_cmd)
{
// Construct the IAR C-STAT command line.
cmList icstat_cmd{ runCmd, cmList::EmptyElements::Yes };
std::string icstat_analyze{ "analyze" };
std::string icstat_dashdash{ "--" };
std::string stdOut;
std::string stdErr;
int ret;
icstat_cmd.push_back(icstat_analyze);
icstat_cmd.push_back(sourceFile);
icstat_cmd.push_back(icstat_dashdash);
for (auto const& cmd : orig_cmd) {
icstat_cmd.push_back(cmd);
}
// Create the default manifest ruleset file when not found
if (!cmSystemTools::FileExists("cstat_sel_checks.txt")) {
std::string ichecks_cmd = cmSystemTools::GetFilenamePath(orig_cmd[0]);
ichecks_cmd = cmStrCat(ichecks_cmd, "/ichecks --default stdchecks");
if (!cmSystemTools::RunSingleCommand(ichecks_cmd, &stdOut, &stdErr, &ret,
nullptr,
cmSystemTools::OUTPUT_NONE)) {
std::cerr << "Error generating default manifest file '" << ichecks_cmd
<< "'. " << stdOut << '\n';
return 1;
}
}
// Run the IAR C-STAT command line. Capture its output.
if (!cmSystemTools::RunSingleCommand(icstat_cmd, &stdOut, &stdErr, &ret,
nullptr, cmSystemTools::OUTPUT_NONE)) {
std::cerr << "Error running '" << icstat_cmd[0] << "': " << stdOut << '\n';
return 1;
}
if (ret == 0) {
std::cerr << "Warning: C-STAT static analysis reported diagnostics:\n";
} else {
std::cerr << "Error: C-STAT static analysis reported failure:\n";
}
std::cerr << stdOut;
std::cerr << stdErr;
return ret;
}
using CoCompileHandler = int (*)(std::string const&, std::string const&,
std::vector<std::string> const&);
@@ -523,10 +572,11 @@ struct CoCompiler
bool NoOriginalCommand;
};
std::array<CoCompiler, 5> const CoCompilers = {
std::array<CoCompiler, 6> const CoCompilers = {
{ // Table of options and handlers.
{ "--cppcheck=", HandleCppCheck, false },
{ "--cpplint=", HandleCppLint, false },
{ "--icstat=", HandleIcstat, false },
{ "--iwyu=", HandleIWYU, false },
{ "--lwyu=", HandleLWYU, true },
{ "--tidy=", HandleTidy, false } }

View File

@@ -1361,7 +1361,11 @@ if(WIN32)
endif()
if(CMake_TEST_IAR_TOOLCHAINS)
add_RunCMake_test(IAR -DCMake_TEST_IAR_TOOLCHAINS=${CMake_TEST_IAR_TOOLCHAINS})
add_executable(pseudo_icstat pseudo_icstat.c)
add_RunCMake_test(IAR
-DCMake_TEST_IAR_TOOLCHAINS=${CMake_TEST_IAR_TOOLCHAINS}
-DPSEUDO_ICSTAT=$<TARGET_FILE:pseudo_icstat>
)
set_property(TEST RunCMake.IAR APPEND PROPERTY LABELS "IAR")
endif()
if(CMake_TEST_TICLANG_TOOLCHAINS)

View File

@@ -1,6 +1,7 @@
^__run_co_compile missing command to run. Looking for one or more of the following:
--cppcheck=
--cpplint=
--icstat=
--iwyu=
--lwyu=
--tidy=

View File

@@ -10,8 +10,9 @@ endif()
function(run_toolchain case)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
run_cmake_with_options(${case} ${ARGN})
run_cmake_with_options(${case} "-DPSEUDO_ICSTAT=${PSEUDO_ICSTAT}" ${ARGN})
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .)
endfunction()
@@ -61,12 +62,24 @@ foreach(_iar_toolchain IN LISTS _iar_toolchains)
-DCMAKE_EXE_LINKER_FLAGS=${LINK_OPTS}
)
run_toolchain(iar-c-bad
-DCMAKE_SYSTEM_NAME=Generic
-DCMAKE_C_COMPILER=${_iar_toolchain}
-DCMAKE_EXE_LINKER_FLAGS=${LINK_OPTS}
)
run_toolchain(iar-cxx
-DCMAKE_SYSTEM_NAME=Generic
-DCMAKE_CXX_COMPILER=${_iar_toolchain}
-DCMAKE_EXE_LINKER_FLAGS=${LINK_OPTS}
)
run_toolchain(iar-cxx-bad
-DCMAKE_SYSTEM_NAME=Generic
-DCMAKE_CXX_COMPILER=${_iar_toolchain}
-DCMAKE_EXE_LINKER_FLAGS=${LINK_OPTS}
)
run_toolchain(iar-asm
-DCMAKE_SYSTEM_NAME=Generic
-DCMAKE_ASM_COMPILER=${IAR_ASSEMBLER}

View File

@@ -0,0 +1 @@
[^0]

View File

@@ -0,0 +1,3 @@
Error: C-STAT static analysis reported failure:
stdout from bad command line arg '-bad'
stderr from bad command line arg '-bad'

View File

@@ -0,0 +1,5 @@
enable_language(C)
set(CMAKE_C_ICSTAT "${PSEUDO_ICSTAT}" -bad)
add_executable(exec-c module.c)
target_compile_options(exec-c PRIVATE -e)
target_link_options(exec-c PRIVATE ${LINKER_OPTS})

View File

@@ -0,0 +1 @@
Warning: C-STAT static analysis reported diagnostics.*Severity

View File

@@ -1,5 +1,5 @@
enable_language(C)
set(CMAKE_C_ICSTAT "${PSEUDO_ICSTAT}" -some -args)
add_executable(exec-c module.c)
target_compile_options(exec-c PRIVATE -e)
target_link_options(exec-c PRIVATE ${LINKER_OPTS})

View File

@@ -0,0 +1 @@
[^0]

View File

@@ -0,0 +1,3 @@
Error: C-STAT static analysis reported failure:
stdout from bad command line arg '-bad'
stderr from bad command line arg '-bad'

View File

@@ -0,0 +1,5 @@
enable_language(CXX)
set(CMAKE_CXX_ICSTAT "${PSEUDO_ICSTAT}" -bad)
add_executable(exec-cxx module.cxx)
target_compile_options(exec-cxx PRIVATE -e)
target_link_options(exec-cxx PRIVATE ${LINKER_OPTS})

View File

@@ -0,0 +1 @@
Warning: C-STAT static analysis reported diagnostics.*Severity

View File

@@ -1,5 +1,5 @@
enable_language(CXX)
set(CMAKE_CXX_ICSTAT "${PSEUDO_ICSTAT}" -some -args)
add_executable(exec-cxx module.cxx)
target_compile_options(exec-cxx PRIVATE -e)
target_link_options(exec-cxx PRIVATE ${LINKER_OPTS})

View File

@@ -0,0 +1,29 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[])
{
int i;
int result = 0;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-bad") == 0) {
fprintf(stdout, "stdout from bad command line arg '-bad'\n");
fprintf(stderr, "stderr from bad command line arg '-bad'\n");
return 1;
}
}
fprintf(stderr,
"\"foo/bar.c\",2 Severity-High[SPC-uninit-var-some]:"
"Variable `i' may be uninitialized.\n\n");
fprintf(stderr,
"\"foo/bar.c\",2 Severity-Medium[MISRAC2012-Rule-8.2_a]:"
"`main' does not have a valid prototype.\n\n");
fprintf(stderr,
"\"foo/bar.c\",2 Severity-Low[MISRAC2012-Rule-21.6]:"
"Use of `stdio.h' is not compliant.\n\n");
fprintf(stderr,
"\"foo/bar.c\",2 Severity-Low[MISRAC2012-Rule-17.7]:"
"The return value of this call to `printf()' is discarded.\n\n");
return result;
}