Selaa lähdekoodia

Merge topic 'add-cppcheck'

311b7b1a Add properties to run cppcheck along with the compiler

Acked-by: Kitware Robot <[email protected]>
Merge-request: !1147
Brad King 8 vuotta sitten
vanhempi
sitoutus
82d9bbf2b7

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

@@ -227,6 +227,7 @@ Properties on Targets
    /prop_tgt/LABELS
    /prop_tgt/LANG_CLANG_TIDY
    /prop_tgt/LANG_COMPILER_LAUNCHER
+   /prop_tgt/LANG_CPPCHECK
    /prop_tgt/LANG_CPPLINT
    /prop_tgt/LANG_INCLUDE_WHAT_YOU_USE
    /prop_tgt/LANG_VISIBILITY_PRESET

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

@@ -302,6 +302,7 @@ Variables that Control the Build
    /variable/CMAKE_IOS_INSTALL_COMBINED
    /variable/CMAKE_LANG_CLANG_TIDY
    /variable/CMAKE_LANG_COMPILER_LAUNCHER
+   /variable/CMAKE_LANG_CPPCHECK
    /variable/CMAKE_LANG_CPPLINT
    /variable/CMAKE_LANG_INCLUDE_WHAT_YOU_USE
    /variable/CMAKE_LANG_VISIBILITY_PRESET

+ 13 - 0
Help/prop_tgt/LANG_CPPCHECK.rst

@@ -0,0 +1,13 @@
+<LANG>_CPPCHECK
+---------------
+
+This property is supported only when ``<LANG>`` is ``C`` or ``CXX``.
+
+Specify a :ref:`;-list <CMake Language Lists>` containing a command line
+for the ``cppcheck`` static analysis tool.  The :ref:`Makefile Generators`
+and the :generator:`Ninja` generator will run ``cppcheck`` along with the
+compiler and report any problems.
+
+This property is initialized by the value of the
+:variable:`CMAKE_<LANG>_CPPCHECK` variable if it is set when a target is
+created.

+ 7 - 0
Help/release/dev/add-cppcheck.rst

@@ -0,0 +1,7 @@
+add-cppcheck
+------------
+
+* A :prop_tgt:`<LANG>_CPPCHECK` target property and supporting
+  :variable:`CMAKE_<LANG>_CPPCHECK` variable were introduced to tell
+  the :ref:`Makefile Generators` and the :generator:`Ninja` generator to
+  run ``cppcheck`` with the compiler for ``C`` and ``CXX`` languages.

+ 6 - 0
Help/variable/CMAKE_LANG_CPPCHECK.rst

@@ -0,0 +1,6 @@
+CMAKE_<LANG>_CPPCHECK
+---------------------
+
+Default value for :prop_tgt:`<LANG>_CPPCHECK` 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``.

+ 10 - 2
Source/cmMakefileTargetGenerator.cxx

@@ -633,7 +633,10 @@ void cmMakefileTargetGenerator::WriteObjectBuildFile(
       const char* tidy = this->GeneratorTarget->GetProperty(tidy_prop);
       std::string const cpplint_prop = lang + "_CPPLINT";
       const char* cpplint = this->GeneratorTarget->GetProperty(cpplint_prop);
-      if ((iwyu && *iwyu) || (tidy && *tidy) || (cpplint && *cpplint)) {
+      std::string const cppcheck_prop = lang + "_CPPCHECK";
+      const char* cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop);
+      if ((iwyu && *iwyu) || (tidy && *tidy) || (cpplint && *cpplint) ||
+          (cppcheck && *cppcheck)) {
         std::string run_iwyu = "$(CMAKE_COMMAND) -E __run_iwyu";
         if (iwyu && *iwyu) {
           run_iwyu += " --iwyu=";
@@ -647,7 +650,12 @@ void cmMakefileTargetGenerator::WriteObjectBuildFile(
           run_iwyu += " --cpplint=";
           run_iwyu += this->LocalGenerator->EscapeForShell(cpplint);
         }
-        if ((tidy && *tidy) || (cpplint && *cpplint)) {
+        if (cppcheck && *cppcheck) {
+          run_iwyu += " --cppcheck=";
+          run_iwyu += this->LocalGenerator->EscapeForShell(cppcheck);
+        }
+        if ((tidy && *tidy) || (cpplint && *cpplint) ||
+            (cppcheck && *cppcheck)) {
           run_iwyu += " --source=";
           run_iwyu += sourceFile;
         }

+ 10 - 2
Source/cmNinjaTargetGenerator.cxx

@@ -617,7 +617,10 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
     const char* tidy = this->GeneratorTarget->GetProperty(tidy_prop);
     std::string const cpplint_prop = lang + "_CPPLINT";
     const char* cpplint = this->GeneratorTarget->GetProperty(cpplint_prop);
-    if ((iwyu && *iwyu) || (tidy && *tidy) || (cpplint && *cpplint)) {
+    std::string const cppcheck_prop = lang + "_CPPCHECK";
+    const char* cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop);
+    if ((iwyu && *iwyu) || (tidy && *tidy) || (cpplint && *cpplint) ||
+        (cppcheck && *cppcheck)) {
       std::string run_iwyu = this->GetLocalGenerator()->ConvertToOutputFormat(
         cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
       run_iwyu += " -E __run_iwyu";
@@ -633,7 +636,12 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
         run_iwyu += " --cpplint=";
         run_iwyu += this->GetLocalGenerator()->EscapeForShell(cpplint);
       }
-      if ((tidy && *tidy) || (cpplint && *cpplint)) {
+      if (cppcheck && *cppcheck) {
+        run_iwyu += " --cppcheck=";
+        run_iwyu += this->GetLocalGenerator()->EscapeForShell(cppcheck);
+      }
+      if ((tidy && *tidy) || (cpplint && *cpplint) ||
+          (cppcheck && *cppcheck)) {
         run_iwyu += " --source=$in";
       }
       run_iwyu += " -- ";

+ 2 - 0
Source/cmTarget.cxx

@@ -262,6 +262,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     this->SetPropertyDefault("C_CLANG_TIDY", nullptr);
     this->SetPropertyDefault("C_COMPILER_LAUNCHER", nullptr);
     this->SetPropertyDefault("C_CPPLINT", nullptr);
+    this->SetPropertyDefault("C_CPPCHECK", nullptr);
     this->SetPropertyDefault("C_INCLUDE_WHAT_YOU_USE", nullptr);
     this->SetPropertyDefault("LINK_WHAT_YOU_USE", nullptr);
     this->SetPropertyDefault("C_STANDARD", nullptr);
@@ -270,6 +271,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     this->SetPropertyDefault("CXX_CLANG_TIDY", nullptr);
     this->SetPropertyDefault("CXX_COMPILER_LAUNCHER", nullptr);
     this->SetPropertyDefault("CXX_CPPLINT", nullptr);
+    this->SetPropertyDefault("CXX_CPPCHECK", nullptr);
     this->SetPropertyDefault("CXX_INCLUDE_WHAT_YOU_USE", nullptr);
     this->SetPropertyDefault("CXX_STANDARD", nullptr);
     this->SetPropertyDefault("CXX_STANDARD_REQUIRED", nullptr);

+ 60 - 7
Source/cmcmd.cxx

@@ -297,6 +297,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
       std::string sourceFile;
       std::string lwyu;
       std::string cpplint;
+      std::string cppcheck;
       for (std::string::size_type cc = 2; cc < args.size(); cc++) {
         std::string const& arg = args[cc];
         if (arg == "--") {
@@ -311,6 +312,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
           lwyu = arg.substr(7);
         } else if (doing_options && cmHasLiteralPrefix(arg, "--cpplint=")) {
           cpplint = arg.substr(10);
+        } else if (doing_options && cmHasLiteralPrefix(arg, "--cppcheck=")) {
+          cppcheck = arg.substr(11);
         } else if (doing_options) {
           std::cerr << "__run_iwyu given unknown argument: " << arg << "\n";
           return 1;
@@ -318,14 +321,16 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
           orig_cmd.push_back(arg);
         }
       }
-      if (tidy.empty() && iwyu.empty() && lwyu.empty() && cpplint.empty()) {
-        std::cerr << "__run_iwyu missing --cpplint=, --iwyu=, --lwyu=, and/or"
-                     " --tidy=\n";
+      if (tidy.empty() && iwyu.empty() && lwyu.empty() && cpplint.empty() &&
+          cppcheck.empty()) {
+        std::cerr << "__run_iwyu missing --cpplint=, --iwyu=, --lwyu=, "
+                     "--cppcheck= and/or --tidy=\n";
         return 1;
       }
-      if ((!cpplint.empty() || !tidy.empty()) && sourceFile.empty()) {
-        std::cerr << "__run_iwyu --cpplint= and/or __run_iwyu --tidy="
-                     " require --source=\n";
+      if ((!cpplint.empty() || !tidy.empty() || !cppcheck.empty()) &&
+          sourceFile.empty()) {
+        std::cerr << "__run_iwyu --cpplint=, __run_iwyu --tidy="
+                     ", __run_iwyu --cppcheck require --source=\n";
         return 1;
       }
       if (orig_cmd.empty() && lwyu.empty()) {
@@ -445,8 +450,56 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
         }
       }
 
+      if (!cppcheck.empty()) {
+        // Construct the cpplint command line.
+        std::vector<std::string> cppcheck_cmd;
+        cmSystemTools::ExpandListArgument(cppcheck, cppcheck_cmd, true);
+        // extract all the -D, -U, and -I options from the compile line
+        for (size_t i = 0; i < orig_cmd.size(); i++) {
+          std::string& opt = orig_cmd[i];
+          if (opt.size() > 2) {
+            if ((opt[0] == '-') &&
+                ((opt[1] == 'D') || (opt[1] == 'I') || (opt[1] == 'U'))) {
+              cppcheck_cmd.push_back(opt);
+#if defined(_WIN32)
+            } else if ((opt[0] == '/') &&
+                       ((opt[1] == 'D') || (opt[1] == 'I') ||
+                        (opt[1] == 'U'))) {
+              std::string optcopy = opt;
+              optcopy[0] = '-';
+              cppcheck_cmd.push_back(optcopy);
+#endif
+            }
+          }
+        }
+        // add the source file
+        cppcheck_cmd.push_back(sourceFile);
+
+        // Run the cpplint command line.  Capture its output.
+        std::string stdOut;
+        if (!cmSystemTools::RunSingleCommand(cppcheck_cmd, &stdOut, &stdOut,
+                                             &ret, nullptr,
+                                             cmSystemTools::OUTPUT_NONE)) {
+          std::cerr << "Error running '" << cppcheck_cmd[0] << "': " << stdOut
+                    << "\n";
+          return 1;
+        }
+        // Output the output from cpplint to stderr
+        if (stdOut.find("(error)") != std::string::npos ||
+            stdOut.find("(warning)") != std::string::npos ||
+            stdOut.find("(style)") != std::string::npos ||
+            stdOut.find("(performance)") != std::string::npos ||
+            stdOut.find("(portability)") != std::string::npos ||
+            stdOut.find("(information)") != std::string::npos) {
+          std::cerr << "Warning: cppcheck reported diagnostics:\n";
+        }
+        std::cerr << stdOut;
+      }
+      // ignore the cppcheck error code because it is likely to have them
+      // from bad -D stuff
       ret = 0;
-      // Now run the real compiler command and return its result value.
+      // Now run the real compiler command and return its result value
+      // unless we are lwyu
       if (lwyu.empty() &&
           !cmSystemTools::RunSingleCommand(
             orig_cmd, nullptr, nullptr, &ret, nullptr,

+ 2 - 0
Tests/RunCMake/CMakeLists.txt

@@ -343,9 +343,11 @@ if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
   add_executable(pseudo_tidy pseudo_tidy.c)
   add_executable(pseudo_iwyu pseudo_iwyu.c)
   add_executable(pseudo_cpplint pseudo_cpplint.c)
+  add_executable(pseudo_cppcheck pseudo_cppcheck.c)
   add_RunCMake_test(ClangTidy -DPSEUDO_TIDY=$<TARGET_FILE:pseudo_tidy>)
   add_RunCMake_test(IncludeWhatYouUse -DPSEUDO_IWYU=$<TARGET_FILE:pseudo_iwyu>)
   add_RunCMake_test(Cpplint -DPSEUDO_CPPLINT=$<TARGET_FILE:pseudo_cpplint>)
+  add_RunCMake_test(Cppcheck -DPSEUDO_CPPCHECK=$<TARGET_FILE:pseudo_cppcheck>)
   if(DEFINED CMake_TEST_CUDA)
     list(APPEND CompilerLauncher_ARGS -DCMake_TEST_CUDA=${CMake_TEST_CUDA})
   endif()

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

@@ -1 +1 @@
-^__run_iwyu missing --cpplint=, --iwyu=, --lwyu=, and/or --tidy=$
+^__run_iwyu missing --cpplint=, --iwyu=, --lwyu=, --cppcheck= and/or --tidy=$

+ 1 - 0
Tests/RunCMake/Cppcheck/C-Build-stdout.txt

@@ -0,0 +1 @@
+.*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 1 - 0
Tests/RunCMake/Cppcheck/C-launch-Build-stdout.txt

@@ -0,0 +1 @@
+.*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 3 - 0
Tests/RunCMake/Cppcheck/C-launch.cmake

@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(C.cmake)

+ 4 - 0
Tests/RunCMake/Cppcheck/C.cmake

@@ -0,0 +1,4 @@
+
+enable_language(C)
+set(CMAKE_C_CPPCHECK "${PSEUDO_CPPCHECK}")
+add_executable(main main.c)

+ 3 - 0
Tests/RunCMake/Cppcheck/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.7)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 1 - 0
Tests/RunCMake/Cppcheck/CXX-Build-stdout.txt

@@ -0,0 +1 @@
+.*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 1 - 0
Tests/RunCMake/Cppcheck/CXX-launch-Build-stdout.txt

@@ -0,0 +1 @@
+.*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 3 - 0
Tests/RunCMake/Cppcheck/CXX-launch.cmake

@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(CXX.cmake)

+ 3 - 0
Tests/RunCMake/Cppcheck/CXX.cmake

@@ -0,0 +1,3 @@
+enable_language(CXX)
+set(CMAKE_CXX_CPPCHECK "${PSEUDO_CPPCHECK}")
+add_executable(main main.cxx)

+ 22 - 0
Tests/RunCMake/Cppcheck/RunCMakeTest.cmake

@@ -0,0 +1,22 @@
+include(RunCMake)
+
+set(RunCMake_TEST_OPTIONS "-DPSEUDO_CPPCHECK=${PSEUDO_CPPCHECK}")
+
+function(run_cppcheck lang)
+  # Use a single build tree for tests without cleaning.
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${lang}-build")
+  set(RunCMake_TEST_NO_CLEAN 1)
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+  run_cmake(${lang})
+  set(RunCMake_TEST_OUTPUT_MERGE 1)
+  run_cmake_command(${lang}-Build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+run_cppcheck(C)
+run_cppcheck(CXX)
+
+if(NOT RunCMake_GENERATOR STREQUAL "Watcom WMake")
+  run_cppcheck(C-launch)
+  run_cppcheck(CXX-launch)
+endif()

+ 4 - 0
Tests/RunCMake/Cppcheck/main.c

@@ -0,0 +1,4 @@
+int main(void)
+{
+  return 0;
+}

+ 4 - 0
Tests/RunCMake/Cppcheck/main.cxx

@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}

+ 21 - 0
Tests/RunCMake/pseudo_cppcheck.c

@@ -0,0 +1,21 @@
+#include <stdio.h>
+
+int main(void)
+{
+  fprintf(stderr,
+          "[/foo/bar.c:2]: (error) Array 'abc[10]' accessed at index 12,"
+          " which is out of bounds.\n");
+  fprintf(stderr, "[/foo/bar.c:2]: (warning) Member variable 'foo::bar' is "
+                  "not initialized in the constructor.\n");
+  fprintf(stderr, "[/foo/bar.c:2]: (style) C-style pointer casting.\n");
+  fprintf(stderr, "[/foo/bar.c:2]: (performance) Variable 'm_message' is "
+                  "assigned in constructor body. Consider performing "
+                  "initialization in initialization list.\n");
+  fprintf(stderr, "[/foo/bar.c:2]: (portability) scanf without field width "
+                  "limits can crash with huge input data on some versions of "
+                  "libc\n");
+  fprintf(stderr, "[/foo/bar.c:2]: (information) cannot find all the include "
+                  "files (use --check-config for details)\n");
+  // we allow this to return 1 as we ignore it
+  return 1;
+}