ctest: add support for memcheck using Dr. Memory

Fixes: #19788
This commit is contained in:
Dietmar Scheidl 2019-11-19 11:44:16 +01:00 committed by Brad King
parent 2db0a65f56
commit 676befdf52
7 changed files with 200 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {
@ -449,6 +478,10 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
this->CTest->GetCTestConfiguration("ValgrindCommand"))) {
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 =
@ -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)
{

View File

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