summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Help/manual/ctest.1.rst14
-rw-r--r--Help/release/dev/ctest-drmemory-support.rst5
-rw-r--r--Help/variable/CTEST_MEMORYCHECK_TYPE.rst2
-rw-r--r--Modules/CTest.cmake2
-rw-r--r--Modules/DartConfiguration.tcl.in2
-rw-r--r--Source/CTest/cmCTestMemCheckHandler.cxx173
-rw-r--r--Source/CTest/cmCTestMemCheckHandler.h4
7 files changed, 200 insertions, 2 deletions
diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst
index 17bddb6789..25cb639669 100644
--- a/Help/manual/ctest.1.rst
+++ b/Help/manual/ctest.1.rst
@@ -1113,6 +1113,20 @@ Additional configuration settings include:
* `CTest Script`_ variable: none
* :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
diff --git a/Help/release/dev/ctest-drmemory-support.rst b/Help/release/dev/ctest-drmemory-support.rst
new file mode 100644
index 0000000000..b329995155
--- /dev/null
+++ b/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.
diff --git a/Help/variable/CTEST_MEMORYCHECK_TYPE.rst b/Help/variable/CTEST_MEMORYCHECK_TYPE.rst
index b8b4c30be5..4e7d5c0ef3 100644
--- a/Help/variable/CTEST_MEMORYCHECK_TYPE.rst
+++ b/Help/variable/CTEST_MEMORYCHECK_TYPE.rst
@@ -3,6 +3,6 @@ CTEST_MEMORYCHECK_TYPE
Specify the CTest ``MemoryCheckType`` setting
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
``UndefinedBehaviorSanitizer``.
diff --git a/Modules/CTest.cmake b/Modules/CTest.cmake
index 3a111ca92c..1a51bc8e93 100644
--- a/Modules/CTest.cmake
+++ b/Modules/CTest.cmake
@@ -174,7 +174,7 @@ if(BUILD_TESTING)
"How many times to retry timed-out CTest submissions.")
find_program(MEMORYCHECK_COMMAND
- NAMES purify valgrind boundscheck
+ NAMES purify valgrind boundscheck drmemory
PATHS
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Rational Software\\Purify\\Setup;InstallFolder]"
DOC "Path to the memory checking command, used for memory error detection."
diff --git a/Modules/DartConfiguration.tcl.in b/Modules/DartConfiguration.tcl.in
index e4513b363d..086ba07438 100644
--- a/Modules/DartConfiguration.tcl.in
+++ b/Modules/DartConfiguration.tcl.in
@@ -69,6 +69,8 @@ CompilerVersion: @CMAKE_CXX_COMPILER_VERSION@
PurifyCommand: @PURIFYCOMMAND@
ValgrindCommand: @VALGRIND_COMMAND@
ValgrindCommandOptions: @VALGRIND_COMMAND_OPTIONS@
+DrMemoryCommand: @DRMEMORY_COMMAND@
+DrMemoryCommandOptions: @DRMEMORY_COMMAND_OPTIONS@
MemoryCheckType: @MEMORYCHECK_TYPE@
MemoryCheckSanitizerOptions: @MEMORYCHECK_SANITIZER_OPTIONS@
MemoryCheckCommand: @MEMORYCHECK_COMMAND@
diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx
index 8093622cc4..c1ecaf125d 100644
--- a/Source/CTest/cmCTestMemCheckHandler.cxx
+++ b/Source/CTest/cmCTestMemCheckHandler.cxx
@@ -2,9 +2,11 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestMemCheckHandler.h"
+#include <algorithm>
#include <chrono>
#include <cstring>
#include <iostream>
+#include <iterator>
#include <sstream>
#include <utility>
@@ -12,6 +14,7 @@
#include "cmsys/Glob.hxx"
#include "cmsys/RegularExpression.hxx"
+#include "cmAlgorithms.h"
#include "cmCTest.h"
#include "cmDuration.h"
#include "cmSystemTools.h"
@@ -165,6 +168,10 @@ void cmCTestMemCheckHandler::GenerateTestCommand(
std::string index = std::to_string(test);
std::string memcheckcommand =
cmSystemTools::ConvertToOutputPath(this->MemoryTester);
+
+ std::vector<std::string> dirs;
+ bool nextArgIsDir = false;
+
for (std::string arg : this->MemoryTesterDynamicOptions) {
std::string::size_type pos = arg.find("??");
if (pos != std::string::npos) {
@@ -174,6 +181,16 @@ void cmCTestMemCheckHandler::GenerateTestCommand(
memcheckcommand += " \"";
memcheckcommand += arg;
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.
// This is used for memory testing programs that pass options
@@ -205,6 +222,11 @@ void cmCTestMemCheckHandler::GenerateTestCommand(
memcheckcommand += " " + memTesterEnvironmentVariable;
args.push_back(memTesterEnvironmentVariable);
}
+
+ for (std::string const& dir : dirs) {
+ cmSystemTools::MakeDirectory(dir);
+ }
+
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Memory check command: " << memcheckcommand << std::endl,
this->Quiet);
@@ -297,6 +319,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(cmXMLWriter& xml)
case cmCTestMemCheckHandler::VALGRIND:
xml.Attribute("Checker", "Valgrind");
break;
+ case cmCTestMemCheckHandler::DRMEMORY:
+ xml.Attribute("Checker", "DrMemory");
+ break;
case cmCTestMemCheckHandler::PURIFY:
xml.Attribute("Checker", "Purify");
break;
@@ -434,6 +459,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
if (testerName.find("valgrind") != std::string::npos ||
this->CTest->GetCTestConfiguration("MemoryCheckType") == "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) {
this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
} else if (testerName.find("BC") != std::string::npos) {
@@ -450,6 +479,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
this->MemoryTester = this->CTest->GetCTestConfiguration("ValgrindCommand");
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(
this->CTest->GetCTestConfiguration("BoundsCheckerCommand"))) {
this->MemoryTester =
this->CTest->GetCTestConfiguration("BoundsCheckerCommand");
@@ -495,6 +528,8 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
} else if (checkType == "Valgrind") {
this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+ } else if (checkType == "DrMemory") {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::DRMEMORY;
}
}
if (this->MemoryTester.empty()) {
@@ -516,6 +551,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
.empty()) {
memoryTesterOptions =
this->CTest->GetCTestConfiguration("ValgrindCommandOptions");
+ } else if (!this->CTest->GetCTestConfiguration("DrMemoryCommandOptions")
+ .empty()) {
+ memoryTesterOptions =
+ this->CTest->GetCTestConfiguration("DrMemoryCommandOptions");
}
this->MemoryTesterOptions =
cmSystemTools::ParseArguments(memoryTesterOptions);
@@ -551,6 +590,64 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
this->MemoryTesterOutputFile);
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: {
std::string outputFile;
#ifdef _WIN32
@@ -664,6 +761,8 @@ bool cmCTestMemCheckHandler::ProcessMemCheckOutput(const std::string& str,
switch (this->MemoryTesterStyle) {
case cmCTestMemCheckHandler::VALGRIND:
return this->ProcessMemCheckValgrindOutput(str, log, results);
+ case cmCTestMemCheckHandler::DRMEMORY:
+ return this->ProcessMemCheckDrMemoryOutput(str, log, results);
case cmCTestMemCheckHandler::PURIFY:
return this->ProcessMemCheckPurifyOutput(str, log, results);
case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
@@ -929,6 +1028,47 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput(
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(
const std::string& str, std::string& log, std::vector<int>& results)
{
@@ -988,6 +1128,8 @@ void cmCTestMemCheckHandler::PostProcessTest(cmCTestTestResult& res, int test)
this->Quiet);
if (this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER) {
this->PostProcessBoundsCheckerTest(res, test);
+ } else if (this->MemoryTesterStyle == cmCTestMemCheckHandler::DRMEMORY) {
+ this->PostProcessDrMemoryTest(res, test);
} else {
std::vector<std::string> files;
this->TestOutputFileNames(test, files);
@@ -1042,6 +1184,37 @@ void cmCTestMemCheckHandler::PostProcessBoundsCheckerTest(
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,
std::string const& ofile)
{
diff --git a/Source/CTest/cmCTestMemCheckHandler.h b/Source/CTest/cmCTestMemCheckHandler.h
index eda65f7b98..52667f8cfe 100644
--- a/Source/CTest/cmCTestMemCheckHandler.h
+++ b/Source/CTest/cmCTestMemCheckHandler.h
@@ -43,6 +43,7 @@ private:
UNKNOWN = 0,
VALGRIND,
PURIFY,
+ DRMEMORY,
BOUNDS_CHECKER,
// checkers after here do not use the standard error list
ADDRESS_SANITIZER,
@@ -132,6 +133,8 @@ private:
std::vector<int>& results);
bool ProcessMemCheckValgrindOutput(const std::string& str, std::string& log,
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,
std::vector<int>& results);
bool ProcessMemCheckSanitizerOutput(const std::string& str, std::string& log,
@@ -142,6 +145,7 @@ private:
void PostProcessTest(cmCTestTestResult& res, int test);
void PostProcessBoundsCheckerTest(cmCTestTestResult& res, int test);
+ void PostProcessDrMemoryTest(cmCTestTestResult& res, int test);
//! append MemoryTesterOutputFile to the test log
void AppendMemTesterOutput(cmCTestTestHandler::cmCTestTestResult& res,