Browse Source

IAR: Add support for C-STAT static analysis

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

Closes: #26844
Felipe Torrezan 9 months ago
parent
commit
c7d2a17253

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -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

+ 1 - 0
Help/manual/cmake-variables.7.rst

@@ -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

+ 8 - 7
Help/prop_sf/SKIP_LINTING.rst

@@ -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

+ 20 - 0
Help/prop_tgt/LANG_ICSTAT.rst

@@ -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.

+ 8 - 0
Help/release/dev/iar-add-icstat-support.rst

@@ -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.

+ 8 - 0
Help/variable/CMAKE_LANG_ICSTAT.rst

@@ -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``.

+ 23 - 1
Source/cmCommonTargetGenerator.cxx

@@ -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 +=

+ 3 - 0
Source/cmTarget.cxx

@@ -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

+ 51 - 1
Source/cmcmd.cxx

@@ -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 } }

+ 5 - 1
Tests/RunCMake/CMakeLists.txt

@@ -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)

+ 1 - 0
Tests/RunCMake/CommandLine/E___run_co_compile-no-iwyu-stderr.txt

@@ -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=

+ 14 - 1
Tests/RunCMake/IAR/RunCMakeTest.cmake

@@ -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}

+ 1 - 0
Tests/RunCMake/IAR/iar-c-bad-build-result.txt

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

+ 3 - 0
Tests/RunCMake/IAR/iar-c-bad-build-stdout.txt

@@ -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'

+ 5 - 0
Tests/RunCMake/IAR/iar-c-bad.cmake

@@ -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})

+ 1 - 0
Tests/RunCMake/IAR/iar-c-build-stdout.txt

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

+ 1 - 1
Tests/RunCMake/IAR/iar-c.cmake

@@ -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})

+ 1 - 0
Tests/RunCMake/IAR/iar-cxx-bad-build-result.txt

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

+ 3 - 0
Tests/RunCMake/IAR/iar-cxx-bad-build-stdout.txt

@@ -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'

+ 5 - 0
Tests/RunCMake/IAR/iar-cxx-bad.cmake

@@ -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})

+ 1 - 0
Tests/RunCMake/IAR/iar-cxx-build-stdout.txt

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

+ 1 - 1
Tests/RunCMake/IAR/iar-cxx.cmake

@@ -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})

+ 29 - 0
Tests/RunCMake/pseudo_icstat.c

@@ -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;
+}