Browse Source

ctest: add support for memcheck using Dr. Memory

Fixes: #19788
Dietmar Scheidl 6 years ago
parent
commit
676befdf52

+ 14 - 0
Help/manual/ctest.1.rst

@@ -1113,6 +1113,20 @@ Additional configuration settings include:
   * `CTest Script`_ variable: none
   * `CTest Script`_ variable: none
   * :module:`CTest` module variable: ``VALGRIND_COMMAND_OPTIONS``
   * :module:`CTest` module variable: ``VALGRIND_COMMAND_OPTIONS``
 
 
+``DrMemoryCommand``
+  Specify a ``MemoryCheckCommand`` that is known to be a command-line
+  compatible with DrMemory.
+
+  * `CTest Script`_ variable: none
+  * :module:`CTest` module variable: ``DRMEMORY_COMMAND``
+
+``DrMemoryCommandOptions``
+  Specify command-line options to the ``DrMemoryCommand`` tool.
+  They will be placed prior to the test command line.
+
+  * `CTest Script`_ variable: none
+  * :module:`CTest` module variable: ``DRMEMORY_COMMAND_OPTIONS``
+
 .. _`CTest Submit Step`:
 .. _`CTest Submit Step`:
 
 
 CTest Submit Step
 CTest Submit Step

+ 5 - 0
Help/release/dev/ctest-drmemory-support.rst

@@ -0,0 +1,5 @@
+ctest-drmemory-support
+----------------------
+
+* The :manual:`ctest(1)` gained support for Dr. Memory to run
+  memcheck runs.

+ 1 - 1
Help/variable/CTEST_MEMORYCHECK_TYPE.rst

@@ -3,6 +3,6 @@ CTEST_MEMORYCHECK_TYPE
 
 
 Specify the CTest ``MemoryCheckType`` setting
 Specify the CTest ``MemoryCheckType`` setting
 in a :manual:`ctest(1)` dashboard client script.
 in a :manual:`ctest(1)` dashboard client script.
-Valid values are ``Valgrind``, ``Purify``, ``BoundsChecker``, and
+Valid values are ``Valgrind``, ``Purify``, ``BoundsChecker``, ``DrMemory`` and
 ``ThreadSanitizer``, ``AddressSanitizer``, ``LeakSanitizer``, ``MemorySanitizer``, and
 ``ThreadSanitizer``, ``AddressSanitizer``, ``LeakSanitizer``, ``MemorySanitizer``, and
 ``UndefinedBehaviorSanitizer``.
 ``UndefinedBehaviorSanitizer``.

+ 1 - 1
Modules/CTest.cmake

@@ -174,7 +174,7 @@ if(BUILD_TESTING)
     "How many times to retry timed-out CTest submissions.")
     "How many times to retry timed-out CTest submissions.")
 
 
   find_program(MEMORYCHECK_COMMAND
   find_program(MEMORYCHECK_COMMAND
-    NAMES purify valgrind boundscheck
+    NAMES purify valgrind boundscheck drmemory
     PATHS
     PATHS
     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Rational Software\\Purify\\Setup;InstallFolder]"
     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Rational Software\\Purify\\Setup;InstallFolder]"
     DOC "Path to the memory checking command, used for memory error detection."
     DOC "Path to the memory checking command, used for memory error detection."

+ 2 - 0
Modules/DartConfiguration.tcl.in

@@ -69,6 +69,8 @@ CompilerVersion: @CMAKE_CXX_COMPILER_VERSION@
 PurifyCommand: @PURIFYCOMMAND@
 PurifyCommand: @PURIFYCOMMAND@
 ValgrindCommand: @VALGRIND_COMMAND@
 ValgrindCommand: @VALGRIND_COMMAND@
 ValgrindCommandOptions: @VALGRIND_COMMAND_OPTIONS@
 ValgrindCommandOptions: @VALGRIND_COMMAND_OPTIONS@
+DrMemoryCommand: @DRMEMORY_COMMAND@
+DrMemoryCommandOptions: @DRMEMORY_COMMAND_OPTIONS@
 MemoryCheckType: @MEMORYCHECK_TYPE@
 MemoryCheckType: @MEMORYCHECK_TYPE@
 MemoryCheckSanitizerOptions: @MEMORYCHECK_SANITIZER_OPTIONS@
 MemoryCheckSanitizerOptions: @MEMORYCHECK_SANITIZER_OPTIONS@
 MemoryCheckCommand: @MEMORYCHECK_COMMAND@
 MemoryCheckCommand: @MEMORYCHECK_COMMAND@

+ 173 - 0
Source/CTest/cmCTestMemCheckHandler.cxx

@@ -2,9 +2,11 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestMemCheckHandler.h"
 #include "cmCTestMemCheckHandler.h"
 
 
+#include <algorithm>
 #include <chrono>
 #include <chrono>
 #include <cstring>
 #include <cstring>
 #include <iostream>
 #include <iostream>
+#include <iterator>
 #include <sstream>
 #include <sstream>
 #include <utility>
 #include <utility>
 
 
@@ -12,6 +14,7 @@
 #include "cmsys/Glob.hxx"
 #include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
 #include "cmsys/RegularExpression.hxx"
 
 
+#include "cmAlgorithms.h"
 #include "cmCTest.h"
 #include "cmCTest.h"
 #include "cmDuration.h"
 #include "cmDuration.h"
 #include "cmSystemTools.h"
 #include "cmSystemTools.h"
@@ -165,6 +168,10 @@ void cmCTestMemCheckHandler::GenerateTestCommand(
   std::string index = std::to_string(test);
   std::string index = std::to_string(test);
   std::string memcheckcommand =
   std::string memcheckcommand =
     cmSystemTools::ConvertToOutputPath(this->MemoryTester);
     cmSystemTools::ConvertToOutputPath(this->MemoryTester);
+
+  std::vector<std::string> dirs;
+  bool nextArgIsDir = false;
+
   for (std::string arg : this->MemoryTesterDynamicOptions) {
   for (std::string arg : this->MemoryTesterDynamicOptions) {
     std::string::size_type pos = arg.find("??");
     std::string::size_type pos = arg.find("??");
     if (pos != std::string::npos) {
     if (pos != std::string::npos) {
@@ -174,6 +181,16 @@ void cmCTestMemCheckHandler::GenerateTestCommand(
     memcheckcommand += " \"";
     memcheckcommand += " \"";
     memcheckcommand += arg;
     memcheckcommand += arg;
     memcheckcommand += "\"";
     memcheckcommand += "\"";
+
+    if (nextArgIsDir) {
+      nextArgIsDir = false;
+      dirs.push_back(arg);
+    }
+
+    if (this->MemoryTesterStyle == cmCTestMemCheckHandler::DRMEMORY &&
+        (arg == "-logdir" || arg == "-symcache_dir")) {
+      nextArgIsDir = true;
+    }
   }
   }
   // Create a copy of the memory tester environment variable.
   // Create a copy of the memory tester environment variable.
   // This is used for memory testing programs that pass options
   // This is used for memory testing programs that pass options
@@ -205,6 +222,11 @@ void cmCTestMemCheckHandler::GenerateTestCommand(
     memcheckcommand += " " + memTesterEnvironmentVariable;
     memcheckcommand += " " + memTesterEnvironmentVariable;
     args.push_back(memTesterEnvironmentVariable);
     args.push_back(memTesterEnvironmentVariable);
   }
   }
+
+  for (std::string const& dir : dirs) {
+    cmSystemTools::MakeDirectory(dir);
+  }
+
   cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
   cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                      "Memory check command: " << memcheckcommand << std::endl,
                      "Memory check command: " << memcheckcommand << std::endl,
                      this->Quiet);
                      this->Quiet);
@@ -297,6 +319,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(cmXMLWriter& xml)
     case cmCTestMemCheckHandler::VALGRIND:
     case cmCTestMemCheckHandler::VALGRIND:
       xml.Attribute("Checker", "Valgrind");
       xml.Attribute("Checker", "Valgrind");
       break;
       break;
+    case cmCTestMemCheckHandler::DRMEMORY:
+      xml.Attribute("Checker", "DrMemory");
+      break;
     case cmCTestMemCheckHandler::PURIFY:
     case cmCTestMemCheckHandler::PURIFY:
       xml.Attribute("Checker", "Purify");
       xml.Attribute("Checker", "Purify");
       break;
       break;
@@ -434,6 +459,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
     if (testerName.find("valgrind") != std::string::npos ||
     if (testerName.find("valgrind") != std::string::npos ||
         this->CTest->GetCTestConfiguration("MemoryCheckType") == "Valgrind") {
         this->CTest->GetCTestConfiguration("MemoryCheckType") == "Valgrind") {
       this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
       this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+    } else if (testerName.find("drmemory") != std::string::npos ||
+               this->CTest->GetCTestConfiguration("MemoryCheckType") ==
+                 "DrMemory") {
+      this->MemoryTesterStyle = cmCTestMemCheckHandler::DRMEMORY;
     } else if (testerName.find("purify") != std::string::npos) {
     } else if (testerName.find("purify") != std::string::npos) {
       this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
       this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
     } else if (testerName.find("BC") != std::string::npos) {
     } else if (testerName.find("BC") != std::string::npos) {
@@ -449,6 +478,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
                this->CTest->GetCTestConfiguration("ValgrindCommand"))) {
                this->CTest->GetCTestConfiguration("ValgrindCommand"))) {
     this->MemoryTester = this->CTest->GetCTestConfiguration("ValgrindCommand");
     this->MemoryTester = this->CTest->GetCTestConfiguration("ValgrindCommand");
     this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
     this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+  } else if (cmSystemTools::FileExists(
+               this->CTest->GetCTestConfiguration("DrMemoryCommand"))) {
+    this->MemoryTester = this->CTest->GetCTestConfiguration("DrMemoryCommand");
+    this->MemoryTesterStyle = cmCTestMemCheckHandler::DRMEMORY;
   } else if (cmSystemTools::FileExists(
   } else if (cmSystemTools::FileExists(
                this->CTest->GetCTestConfiguration("BoundsCheckerCommand"))) {
                this->CTest->GetCTestConfiguration("BoundsCheckerCommand"))) {
     this->MemoryTester =
     this->MemoryTester =
@@ -495,6 +528,8 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
       this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
       this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
     } else if (checkType == "Valgrind") {
     } else if (checkType == "Valgrind") {
       this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
       this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+    } else if (checkType == "DrMemory") {
+      this->MemoryTesterStyle = cmCTestMemCheckHandler::DRMEMORY;
     }
     }
   }
   }
   if (this->MemoryTester.empty()) {
   if (this->MemoryTester.empty()) {
@@ -516,6 +551,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
                 .empty()) {
                 .empty()) {
     memoryTesterOptions =
     memoryTesterOptions =
       this->CTest->GetCTestConfiguration("ValgrindCommandOptions");
       this->CTest->GetCTestConfiguration("ValgrindCommandOptions");
+  } else if (!this->CTest->GetCTestConfiguration("DrMemoryCommandOptions")
+                .empty()) {
+    memoryTesterOptions =
+      this->CTest->GetCTestConfiguration("DrMemoryCommandOptions");
   }
   }
   this->MemoryTesterOptions =
   this->MemoryTesterOptions =
     cmSystemTools::ParseArguments(memoryTesterOptions);
     cmSystemTools::ParseArguments(memoryTesterOptions);
@@ -551,6 +590,64 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
                                                  this->MemoryTesterOutputFile);
                                                  this->MemoryTesterOutputFile);
       break;
       break;
     }
     }
+    case cmCTestMemCheckHandler::DRMEMORY: {
+      std::string tempDrMemoryDir =
+        this->CTest->GetBinaryDir() + "/Testing/Temporary/DrMemory";
+
+      if (!cmContains(this->MemoryTesterOptions, "-quiet")) {
+        this->MemoryTesterOptions.emplace_back("-quiet");
+      }
+
+      if (!cmContains(this->MemoryTesterOptions, "-batch")) {
+        this->MemoryTesterOptions.emplace_back("-batch");
+      }
+
+      this->MemoryTesterDynamicOptions.emplace_back("-logdir");
+      auto logdirOption =
+        std::find(this->MemoryTesterOptions.begin(),
+                  this->MemoryTesterOptions.end(), "-logdir");
+      if (logdirOption == this->MemoryTesterOptions.end()) {
+        // No logdir found in memory tester options
+        std::string drMemoryLogDir = tempDrMemoryDir + "/??";
+        this->MemoryTesterDynamicOptions.push_back(drMemoryLogDir);
+        this->MemoryTesterOutputFile = drMemoryLogDir;
+      } else {
+        // Use logdir found in memory tester options
+        auto logdirLocation = std::next(logdirOption);
+        this->MemoryTesterOutputFile = *logdirLocation;
+        this->MemoryTesterDynamicOptions.push_back(*logdirLocation);
+        this->MemoryTesterOptions.erase(logdirOption, logdirLocation + 1);
+      }
+      this->MemoryTesterOutputFile += "/*/results.txt";
+
+      if (std::find(this->MemoryTesterOptions.begin(),
+                    this->MemoryTesterOptions.end(),
+                    "-symcache_dir") == this->MemoryTesterOptions.end()) {
+        this->MemoryTesterDynamicOptions.emplace_back("-symcache_dir");
+        std::string drMemoryCacheDir = tempDrMemoryDir + "/cache";
+        this->MemoryTesterDynamicOptions.push_back(drMemoryCacheDir);
+      }
+
+      if (!this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
+             .empty()) {
+        if (!cmSystemTools::FileExists(this->CTest->GetCTestConfiguration(
+              "MemoryCheckSuppressionFile"))) {
+          cmCTestLog(this->CTest, ERROR_MESSAGE,
+                     "Cannot find memory checker suppression file: "
+                       << this->CTest->GetCTestConfiguration(
+                            "MemoryCheckSuppressionFile")
+                       << std::endl);
+          return false;
+        }
+        this->MemoryTesterOptions.emplace_back("-suppress");
+        this->MemoryTesterOptions.push_back(
+          this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"));
+      }
+
+      this->MemoryTesterOptions.emplace_back("--");
+
+      break;
+    }
     case cmCTestMemCheckHandler::PURIFY: {
     case cmCTestMemCheckHandler::PURIFY: {
       std::string outputFile;
       std::string outputFile;
 #ifdef _WIN32
 #ifdef _WIN32
@@ -664,6 +761,8 @@ bool cmCTestMemCheckHandler::ProcessMemCheckOutput(const std::string& str,
   switch (this->MemoryTesterStyle) {
   switch (this->MemoryTesterStyle) {
     case cmCTestMemCheckHandler::VALGRIND:
     case cmCTestMemCheckHandler::VALGRIND:
       return this->ProcessMemCheckValgrindOutput(str, log, results);
       return this->ProcessMemCheckValgrindOutput(str, log, results);
+    case cmCTestMemCheckHandler::DRMEMORY:
+      return this->ProcessMemCheckDrMemoryOutput(str, log, results);
     case cmCTestMemCheckHandler::PURIFY:
     case cmCTestMemCheckHandler::PURIFY:
       return this->ProcessMemCheckPurifyOutput(str, log, results);
       return this->ProcessMemCheckPurifyOutput(str, log, results);
     case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
     case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
@@ -929,6 +1028,47 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput(
   return defects == 0;
   return defects == 0;
 }
 }
 
 
+bool cmCTestMemCheckHandler::ProcessMemCheckDrMemoryOutput(
+  const std::string& str, std::string& log, std::vector<int>& results)
+{
+  std::vector<std::string> lines;
+  cmsys::SystemTools::Split(str, lines);
+
+  cmsys::RegularExpression drMemoryError("^Error #[0-9]+");
+
+  cmsys::RegularExpression unaddressableAccess("UNADDRESSABLE ACCESS");
+  cmsys::RegularExpression uninitializedRead("UNINITIALIZED READ");
+  cmsys::RegularExpression invalidHeapArgument("INVALID HEAP ARGUMENT");
+  cmsys::RegularExpression leak("LEAK");
+  cmsys::RegularExpression handleLeak("HANDLE LEAK");
+
+  int defects = 0;
+
+  std::ostringstream ostr;
+  for (const auto& l : lines) {
+    ostr << l << std::endl;
+    if (drMemoryError.find(l)) {
+      defects++;
+      if (unaddressableAccess.find(l)) {
+        results[cmCTestMemCheckHandler::UMR]++;
+      } else if (uninitializedRead.find(l)) {
+        results[cmCTestMemCheckHandler::UMR]++;
+      } else if (leak.find(l)) {
+        results[cmCTestMemCheckHandler::MLK]++;
+      } else if (handleLeak.find(l)) {
+        results[cmCTestMemCheckHandler::MLK]++;
+      } else if (invalidHeapArgument.find(l)) {
+        results[cmCTestMemCheckHandler::FMM]++;
+      }
+    }
+  }
+
+  log = ostr.str();
+
+  this->DefectCount += defects;
+  return defects == 0;
+}
+
 bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput(
 bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput(
   const std::string& str, std::string& log, std::vector<int>& results)
   const std::string& str, std::string& log, std::vector<int>& results)
 {
 {
@@ -988,6 +1128,8 @@ void cmCTestMemCheckHandler::PostProcessTest(cmCTestTestResult& res, int test)
                      this->Quiet);
                      this->Quiet);
   if (this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER) {
   if (this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER) {
     this->PostProcessBoundsCheckerTest(res, test);
     this->PostProcessBoundsCheckerTest(res, test);
+  } else if (this->MemoryTesterStyle == cmCTestMemCheckHandler::DRMEMORY) {
+    this->PostProcessDrMemoryTest(res, test);
   } else {
   } else {
     std::vector<std::string> files;
     std::vector<std::string> files;
     this->TestOutputFileNames(test, files);
     this->TestOutputFileNames(test, files);
@@ -1042,6 +1184,37 @@ void cmCTestMemCheckHandler::PostProcessBoundsCheckerTest(
                      this->Quiet);
                      this->Quiet);
 }
 }
 
 
+void cmCTestMemCheckHandler::PostProcessDrMemoryTest(
+  cmCTestTestHandler::cmCTestTestResult& res, int test)
+{
+  std::string drMemoryLogDir = this->MemoryTesterOutputFile.substr(
+    0, this->MemoryTesterOutputFile.find("/*/results.txt"));
+
+  // replace placeholder of test
+  std::string::size_type pos = drMemoryLogDir.find("??");
+  if (pos != std::string::npos) {
+    drMemoryLogDir.replace(pos, 2, std::to_string(test));
+  }
+
+  cmsys::Glob g;
+  g.FindFiles(drMemoryLogDir + "/resfile.*");
+  const std::vector<std::string>& files = g.GetFiles();
+
+  for (const std::string& f : files) {
+    cmsys::ifstream ifs(f.c_str());
+    if (!ifs) {
+      std::string log = "Cannot read memory tester output file: " + f;
+      cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl);
+      return;
+    }
+    std::string resultFileLocation;
+    cmSystemTools::GetLineFromStream(ifs, resultFileLocation);
+    this->AppendMemTesterOutput(res, resultFileLocation);
+    ifs.close();
+    cmSystemTools::RemoveFile(f);
+  }
+}
+
 void cmCTestMemCheckHandler::AppendMemTesterOutput(cmCTestTestResult& res,
 void cmCTestMemCheckHandler::AppendMemTesterOutput(cmCTestTestResult& res,
                                                    std::string const& ofile)
                                                    std::string const& ofile)
 {
 {

+ 4 - 0
Source/CTest/cmCTestMemCheckHandler.h

@@ -43,6 +43,7 @@ private:
     UNKNOWN = 0,
     UNKNOWN = 0,
     VALGRIND,
     VALGRIND,
     PURIFY,
     PURIFY,
+    DRMEMORY,
     BOUNDS_CHECKER,
     BOUNDS_CHECKER,
     // checkers after here do not use the standard error list
     // checkers after here do not use the standard error list
     ADDRESS_SANITIZER,
     ADDRESS_SANITIZER,
@@ -132,6 +133,8 @@ private:
                              std::vector<int>& results);
                              std::vector<int>& results);
   bool ProcessMemCheckValgrindOutput(const std::string& str, std::string& log,
   bool ProcessMemCheckValgrindOutput(const std::string& str, std::string& log,
                                      std::vector<int>& results);
                                      std::vector<int>& results);
+  bool ProcessMemCheckDrMemoryOutput(const std::string& str, std::string& log,
+                                     std::vector<int>& results);
   bool ProcessMemCheckPurifyOutput(const std::string& str, std::string& log,
   bool ProcessMemCheckPurifyOutput(const std::string& str, std::string& log,
                                    std::vector<int>& results);
                                    std::vector<int>& results);
   bool ProcessMemCheckSanitizerOutput(const std::string& str, std::string& log,
   bool ProcessMemCheckSanitizerOutput(const std::string& str, std::string& log,
@@ -142,6 +145,7 @@ private:
 
 
   void PostProcessTest(cmCTestTestResult& res, int test);
   void PostProcessTest(cmCTestTestResult& res, int test);
   void PostProcessBoundsCheckerTest(cmCTestTestResult& res, int test);
   void PostProcessBoundsCheckerTest(cmCTestTestResult& res, int test);
+  void PostProcessDrMemoryTest(cmCTestTestResult& res, int test);
 
 
   //! append MemoryTesterOutputFile to the test log
   //! append MemoryTesterOutputFile to the test log
   void AppendMemTesterOutput(cmCTestTestHandler::cmCTestTestResult& res,
   void AppendMemTesterOutput(cmCTestTestHandler::cmCTestTestResult& res,