summaryrefslogtreecommitdiffstats
path: root/Source/CTest/cmCTestMemCheckHandler.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/CTest/cmCTestMemCheckHandler.cxx')
-rw-r--r--Source/CTest/cmCTestMemCheckHandler.cxx1068
1 files changed, 1068 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx
new file mode 100644
index 0000000..7d0715f
--- /dev/null
+++ b/Source/CTest/cmCTestMemCheckHandler.cxx
@@ -0,0 +1,1068 @@
+/*============================================================================
+ CMake - Cross Platform Makefile Generator
+ Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+ Distributed under the OSI-approved BSD License (the "License");
+ see accompanying file Copyright.txt for details.
+
+ This software is distributed WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the License for more information.
+============================================================================*/
+
+#include "cmCTestMemCheckHandler.h"
+
+#include "cmCTest.h"
+#include "cmGeneratedFileStream.h"
+#include "cmMakefile.h"
+#include "cmXMLParser.h"
+#include "cmXMLWriter.h"
+#include "cmake.h"
+#include <cmsys/Base64.h>
+#include <cmsys/FStream.hxx>
+#include <cmsys/Glob.hxx>
+#include <cmsys/Process.h>
+#include <cmsys/RegularExpression.hxx>
+
+#include <float.h>
+#include <math.h>
+#include <stdlib.h>
+
+struct CatToErrorType
+{
+ const char* ErrorCategory;
+ int ErrorCode;
+};
+
+static CatToErrorType cmCTestMemCheckBoundsChecker[] = {
+ // Error tags
+ { "Write Overrun", cmCTestMemCheckHandler::ABW },
+ { "Read Overrun", cmCTestMemCheckHandler::ABR },
+ { "Memory Overrun", cmCTestMemCheckHandler::ABW },
+ { "Allocation Conflict", cmCTestMemCheckHandler::FMM },
+ { "Bad Pointer Use", cmCTestMemCheckHandler::FMW },
+ { "Dangling Pointer", cmCTestMemCheckHandler::FMR },
+ { CM_NULLPTR, 0 }
+};
+
+static void xmlReportError(int line, const char* msg, void* data)
+{
+ cmCTest* ctest = (cmCTest*)data;
+ cmCTestLog(ctest, ERROR_MESSAGE, "Error parsing XML in stream at line "
+ << line << ": " << msg << std::endl);
+}
+
+// parse the xml file containing the results of last BoundsChecker run
+class cmBoundsCheckerParser : public cmXMLParser
+{
+public:
+ cmBoundsCheckerParser(cmCTest* c)
+ {
+ this->CTest = c;
+ this->SetErrorCallback(xmlReportError, (void*)c);
+ }
+ void StartElement(const std::string& name, const char** atts) CM_OVERRIDE
+ {
+ if (name == "MemoryLeak" || name == "ResourceLeak") {
+ this->Errors.push_back(cmCTestMemCheckHandler::MLK);
+ } else if (name == "Error" || name == "Dangling Pointer") {
+ this->ParseError(atts);
+ }
+ // Create the log
+ std::ostringstream ostr;
+ ostr << name << ":\n";
+ int i = 0;
+ for (; atts[i] != CM_NULLPTR; i += 2) {
+ ostr << " " << atts[i] << " - " << atts[i + 1] << "\n";
+ }
+ ostr << "\n";
+ this->Log += ostr.str();
+ }
+ void EndElement(const std::string& /*name*/) CM_OVERRIDE {}
+
+ const char* GetAttribute(const char* name, const char** atts)
+ {
+ int i = 0;
+ for (; atts[i] != CM_NULLPTR; ++i) {
+ if (strcmp(name, atts[i]) == 0) {
+ return atts[i + 1];
+ }
+ }
+ return CM_NULLPTR;
+ }
+ void ParseError(const char** atts)
+ {
+ CatToErrorType* ptr = cmCTestMemCheckBoundsChecker;
+ const char* cat = this->GetAttribute("ErrorCategory", atts);
+ if (!cat) {
+ this->Errors.push_back(cmCTestMemCheckHandler::ABW); // do not know
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "No Category found in Bounds checker XML\n");
+ return;
+ }
+ while (ptr->ErrorCategory && cat) {
+ if (strcmp(ptr->ErrorCategory, cat) == 0) {
+ this->Errors.push_back(ptr->ErrorCode);
+ return; // found it we are done
+ }
+ ptr++;
+ }
+ if (ptr->ErrorCategory) {
+ this->Errors.push_back(cmCTestMemCheckHandler::ABW); // do not know
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Found unknown Bounds Checker error " << ptr->ErrorCategory
+ << std::endl);
+ }
+ }
+ cmCTest* CTest;
+ std::vector<int> Errors;
+ std::string Log;
+};
+
+#define BOUNDS_CHECKER_MARKER \
+ "******######*****Begin BOUNDS CHECKER XML******######******"
+
+cmCTestMemCheckHandler::cmCTestMemCheckHandler()
+{
+ this->MemCheck = true;
+ this->CustomMaximumPassedTestOutputSize = 0;
+ this->CustomMaximumFailedTestOutputSize = 0;
+ this->LogWithPID = false;
+}
+
+void cmCTestMemCheckHandler::Initialize()
+{
+ this->Superclass::Initialize();
+ this->LogWithPID = false;
+ this->CustomMaximumPassedTestOutputSize = 0;
+ this->CustomMaximumFailedTestOutputSize = 0;
+ this->MemoryTester = "";
+ this->MemoryTesterDynamicOptions.clear();
+ this->MemoryTesterOptions.clear();
+ this->MemoryTesterStyle = UNKNOWN;
+ this->MemoryTesterOutputFile = "";
+}
+
+int cmCTestMemCheckHandler::PreProcessHandler()
+{
+ if (!this->InitializeMemoryChecking()) {
+ return 0;
+ }
+
+ if (!this->ExecuteCommands(this->CustomPreMemCheck)) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Problem executing pre-memcheck command(s)." << std::endl);
+ return 0;
+ }
+ return 1;
+}
+
+int cmCTestMemCheckHandler::PostProcessHandler()
+{
+ if (!this->ExecuteCommands(this->CustomPostMemCheck)) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Problem executing post-memcheck command(s)." << std::endl);
+ return 0;
+ }
+ return 1;
+}
+
+void cmCTestMemCheckHandler::GenerateTestCommand(
+ std::vector<std::string>& args, int test)
+{
+ std::vector<std::string>::size_type pp;
+ std::string index;
+ std::ostringstream stream;
+ std::string memcheckcommand =
+ cmSystemTools::ConvertToOutputPath(this->MemoryTester.c_str());
+ stream << test;
+ index = stream.str();
+ for (pp = 0; pp < this->MemoryTesterDynamicOptions.size(); pp++) {
+ std::string arg = this->MemoryTesterDynamicOptions[pp];
+ std::string::size_type pos = arg.find("??");
+ if (pos != std::string::npos) {
+ arg.replace(pos, 2, index);
+ }
+ args.push_back(arg);
+ memcheckcommand += " \"";
+ memcheckcommand += arg;
+ memcheckcommand += "\"";
+ }
+ // Create a copy of the memory tester environment variable.
+ // This is used for memory testing programs that pass options
+ // via environment varaibles.
+ std::string memTesterEnvironmentVariable =
+ this->MemoryTesterEnvironmentVariable;
+ for (pp = 0; pp < this->MemoryTesterOptions.size(); pp++) {
+ if (!memTesterEnvironmentVariable.empty()) {
+ // If we are using env to pass options, append all the options to
+ // this string with space separation.
+ memTesterEnvironmentVariable += " " + this->MemoryTesterOptions[pp];
+ }
+ // for regular options just add them to args and memcheckcommand
+ // which is just used for display
+ else {
+ args.push_back(this->MemoryTesterOptions[pp]);
+ memcheckcommand += " \"";
+ memcheckcommand += this->MemoryTesterOptions[pp];
+ memcheckcommand += "\"";
+ }
+ }
+ // if this is an env option type, then add the env string as a single
+ // argument.
+ if (!memTesterEnvironmentVariable.empty()) {
+ std::string::size_type pos = memTesterEnvironmentVariable.find("??");
+ if (pos != std::string::npos) {
+ memTesterEnvironmentVariable.replace(pos, 2, index);
+ }
+ memcheckcommand += " " + memTesterEnvironmentVariable;
+ args.push_back(memTesterEnvironmentVariable);
+ }
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Memory check command: " << memcheckcommand << std::endl,
+ this->Quiet);
+}
+
+void cmCTestMemCheckHandler::InitializeResultsVectors()
+{
+ // fill these members
+ // cmsys::vector<std::string> ResultStrings;
+ // cmsys::vector<std::string> ResultStringsLong;
+ // cmsys::vector<int> GlobalResults;
+ this->ResultStringsLong.clear();
+ this->ResultStrings.clear();
+ this->GlobalResults.clear();
+ // If we are working with style checkers that dynamically fill
+ // the results strings then return.
+ if (this->MemoryTesterStyle > cmCTestMemCheckHandler::BOUNDS_CHECKER) {
+ return;
+ }
+
+ // define the standard set of errors
+ //----------------------------------------------------------------------
+ static const char* cmCTestMemCheckResultStrings[] = {
+ "ABR", "ABW", "ABWL", "COR", "EXU", "FFM", "FIM", "FMM",
+ "FMR", "FMW", "FUM", "IPR", "IPW", "MAF", "MLK", "MPK",
+ "NPR", "ODS", "PAR", "PLK", "UMC", "UMR", CM_NULLPTR
+ };
+ static const char* cmCTestMemCheckResultLongStrings[] = {
+ "Threading Problem",
+ "ABW",
+ "ABWL",
+ "COR",
+ "EXU",
+ "FFM",
+ "FIM",
+ "Mismatched deallocation",
+ "FMR",
+ "FMW",
+ "FUM",
+ "IPR",
+ "IPW",
+ "MAF",
+ "Memory Leak",
+ "Potential Memory Leak",
+ "NPR",
+ "ODS",
+ "Invalid syscall param",
+ "PLK",
+ "Uninitialized Memory Conditional",
+ "Uninitialized Memory Read",
+ CM_NULLPTR
+ };
+ this->GlobalResults.clear();
+ for (int i = 0; cmCTestMemCheckResultStrings[i] != CM_NULLPTR; ++i) {
+ this->ResultStrings.push_back(cmCTestMemCheckResultStrings[i]);
+ this->ResultStringsLong.push_back(cmCTestMemCheckResultLongStrings[i]);
+ this->GlobalResults.push_back(0);
+ }
+}
+
+void cmCTestMemCheckHandler::PopulateCustomVectors(cmMakefile* mf)
+{
+ this->cmCTestTestHandler::PopulateCustomVectors(mf);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_PRE_MEMCHECK",
+ this->CustomPreMemCheck);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_POST_MEMCHECK",
+ this->CustomPostMemCheck);
+
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_MEMCHECK_IGNORE",
+ this->CustomTestsIgnore);
+ std::string cmake = cmSystemTools::GetCMakeCommand();
+ this->CTest->SetCTestConfiguration("CMakeCommand", cmake.c_str(),
+ this->Quiet);
+}
+
+void cmCTestMemCheckHandler::GenerateDartOutput(cmXMLWriter& xml)
+{
+ if (!this->CTest->GetProduceXML()) {
+ return;
+ }
+ this->CTest->StartXML(xml, this->AppendXML);
+ xml.StartElement("DynamicAnalysis");
+ switch (this->MemoryTesterStyle) {
+ case cmCTestMemCheckHandler::VALGRIND:
+ xml.Attribute("Checker", "Valgrind");
+ break;
+ case cmCTestMemCheckHandler::PURIFY:
+ xml.Attribute("Checker", "Purify");
+ break;
+ case cmCTestMemCheckHandler::BOUNDS_CHECKER:
+ xml.Attribute("Checker", "BoundsChecker");
+ break;
+ case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
+ xml.Attribute("Checker", "AddressSanitizer");
+ break;
+ case cmCTestMemCheckHandler::THREAD_SANITIZER:
+ xml.Attribute("Checker", "ThreadSanitizer");
+ break;
+ case cmCTestMemCheckHandler::MEMORY_SANITIZER:
+ xml.Attribute("Checker", "MemorySanitizer");
+ break;
+ case cmCTestMemCheckHandler::UB_SANITIZER:
+ xml.Attribute("Checker", "UndefinedBehaviorSanitizer");
+ break;
+ default:
+ xml.Attribute("Checker", "Unknown");
+ }
+
+ xml.Element("StartDateTime", this->StartTest);
+ xml.Element("StartTestTime", this->StartTestTime);
+ xml.StartElement("TestList");
+ cmCTestMemCheckHandler::TestResultsVector::size_type cc;
+ for (cc = 0; cc < this->TestResults.size(); cc++) {
+ cmCTestTestResult* result = &this->TestResults[cc];
+ std::string testPath = result->Path + "/" + result->Name;
+ xml.Element("Test", this->CTest->GetShortPathToFile(testPath.c_str()));
+ }
+ xml.EndElement(); // TestList
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
+ "-- Processing memory checking output: ", this->Quiet);
+ size_t total = this->TestResults.size();
+ size_t step = total / 10;
+ size_t current = 0;
+ for (cc = 0; cc < this->TestResults.size(); cc++) {
+ cmCTestTestResult* result = &this->TestResults[cc];
+ std::string memcheckstr;
+ std::vector<int> memcheckresults(this->ResultStrings.size(), 0);
+ bool res = this->ProcessMemCheckOutput(result->Output, memcheckstr,
+ memcheckresults);
+ if (res && result->Status == cmCTestMemCheckHandler::COMPLETED) {
+ continue;
+ }
+ this->CleanTestOutput(
+ memcheckstr,
+ static_cast<size_t>(this->CustomMaximumFailedTestOutputSize));
+ this->WriteTestResultHeader(xml, result);
+ xml.StartElement("Results");
+ for (std::vector<int>::size_type kk = 0; kk < memcheckresults.size();
+ ++kk) {
+ if (memcheckresults[kk]) {
+ xml.StartElement("Defect");
+ xml.Attribute("type", this->ResultStringsLong[kk]);
+ xml.Content(memcheckresults[kk]);
+ xml.EndElement(); // Defect
+ }
+ this->GlobalResults[kk] += memcheckresults[kk];
+ }
+ xml.EndElement(); // Results
+
+ xml.StartElement("Log");
+ if (this->CTest->ShouldCompressMemCheckOutput()) {
+ this->CTest->CompressString(memcheckstr);
+ xml.Attribute("compression", "gzip");
+ xml.Attribute("encoding", "base64");
+ }
+ xml.Content(memcheckstr);
+ xml.EndElement(); // Log
+
+ this->WriteTestResultFooter(xml, result);
+ if (current < cc) {
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "#" << std::flush,
+ this->Quiet);
+ current += step;
+ }
+ }
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl, this->Quiet);
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
+ "Memory checking results:" << std::endl, this->Quiet);
+ xml.StartElement("DefectList");
+ for (cc = 0; cc < this->GlobalResults.size(); cc++) {
+ if (this->GlobalResults[cc]) {
+#ifdef cerr
+#undef cerr
+#endif
+ std::cerr.width(35);
+#define cerr no_cerr
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
+ this->ResultStringsLong[cc]
+ << " - " << this->GlobalResults[cc] << std::endl,
+ this->Quiet);
+ xml.StartElement("Defect");
+ xml.Attribute("Type", this->ResultStringsLong[cc]);
+ xml.EndElement();
+ }
+ }
+ xml.EndElement(); // DefectList
+
+ xml.Element("EndDateTime", this->EndTest);
+ xml.Element("EndTestTime", this->EndTestTime);
+ xml.Element("ElapsedMinutes",
+ static_cast<int>(this->ElapsedTestingTime / 6) / 10.0);
+
+ xml.EndElement(); // DynamicAnalysis
+ this->CTest->EndXML(xml);
+}
+
+bool cmCTestMemCheckHandler::InitializeMemoryChecking()
+{
+ this->MemoryTesterEnvironmentVariable = "";
+ this->MemoryTester = "";
+ // Setup the command
+ if (cmSystemTools::FileExists(
+ this->CTest->GetCTestConfiguration("MemoryCheckCommand").c_str())) {
+ this->MemoryTester =
+ this->CTest->GetCTestConfiguration("MemoryCheckCommand");
+ std::string testerName =
+ cmSystemTools::GetFilenameName(this->MemoryTester);
+ // determine the checker type
+ if (testerName.find("valgrind") != std::string::npos ||
+ this->CTest->GetCTestConfiguration("MemoryCheckType") == "Valgrind") {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+ } else if (testerName.find("purify") != std::string::npos) {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
+ } else if (testerName.find("BC") != std::string::npos) {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
+ } else {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::UNKNOWN;
+ }
+ } else if (cmSystemTools::FileExists(
+ this->CTest->GetCTestConfiguration("PurifyCommand").c_str())) {
+ this->MemoryTester = this->CTest->GetCTestConfiguration("PurifyCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
+ } else if (cmSystemTools::FileExists(
+ this->CTest->GetCTestConfiguration("ValgrindCommand")
+ .c_str())) {
+ this->MemoryTester = this->CTest->GetCTestConfiguration("ValgrindCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+ } else if (cmSystemTools::FileExists(
+ this->CTest->GetCTestConfiguration("BoundsCheckerCommand")
+ .c_str())) {
+ this->MemoryTester =
+ this->CTest->GetCTestConfiguration("BoundsCheckerCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
+ }
+ if (this->CTest->GetCTestConfiguration("MemoryCheckType") ==
+ "AddressSanitizer") {
+ this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::ADDRESS_SANITIZER;
+ this->LogWithPID = true; // even if we give the log file the pid is added
+ }
+ if (this->CTest->GetCTestConfiguration("MemoryCheckType") ==
+ "ThreadSanitizer") {
+ this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::THREAD_SANITIZER;
+ this->LogWithPID = true; // even if we give the log file the pid is added
+ }
+ if (this->CTest->GetCTestConfiguration("MemoryCheckType") ==
+ "MemorySanitizer") {
+ this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::MEMORY_SANITIZER;
+ this->LogWithPID = true; // even if we give the log file the pid is added
+ }
+ if (this->CTest->GetCTestConfiguration("MemoryCheckType") ==
+ "UndefinedBehaviorSanitizer") {
+ this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand");
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::UB_SANITIZER;
+ this->LogWithPID = true; // even if we give the log file the pid is added
+ }
+ // Check the MemoryCheckType
+ if (this->MemoryTesterStyle == cmCTestMemCheckHandler::UNKNOWN) {
+ std::string checkType =
+ this->CTest->GetCTestConfiguration("MemoryCheckType");
+ if (checkType == "Purify") {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
+ } else if (checkType == "BoundsChecker") {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
+ } else if (checkType == "Valgrind") {
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
+ }
+ }
+ if (this->MemoryTester.empty()) {
+ cmCTestOptionalLog(this->CTest, WARNING,
+ "Memory checker (MemoryCheckCommand) "
+ "not set, or cannot find the specified program."
+ << std::endl,
+ this->Quiet);
+ return false;
+ }
+
+ // Setup the options
+ std::string memoryTesterOptions;
+ if (!this->CTest->GetCTestConfiguration("MemoryCheckCommandOptions")
+ .empty()) {
+ memoryTesterOptions =
+ this->CTest->GetCTestConfiguration("MemoryCheckCommandOptions");
+ } else if (!this->CTest->GetCTestConfiguration("ValgrindCommandOptions")
+ .empty()) {
+ memoryTesterOptions =
+ this->CTest->GetCTestConfiguration("ValgrindCommandOptions");
+ }
+ this->MemoryTesterOptions =
+ cmSystemTools::ParseArguments(memoryTesterOptions.c_str());
+
+ this->MemoryTesterOutputFile =
+ this->CTest->GetBinaryDir() + "/Testing/Temporary/MemoryChecker.??.log";
+
+ switch (this->MemoryTesterStyle) {
+ case cmCTestMemCheckHandler::VALGRIND: {
+ if (this->MemoryTesterOptions.empty()) {
+ this->MemoryTesterOptions.push_back("-q");
+ this->MemoryTesterOptions.push_back("--tool=memcheck");
+ this->MemoryTesterOptions.push_back("--leak-check=yes");
+ this->MemoryTesterOptions.push_back("--show-reachable=yes");
+ this->MemoryTesterOptions.push_back("--num-callers=50");
+ }
+ if (!this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
+ .empty()) {
+ if (!cmSystemTools::FileExists(
+ this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
+ .c_str())) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find memory checker suppression file: "
+ << this->CTest->GetCTestConfiguration(
+ "MemoryCheckSuppressionFile")
+ << std::endl);
+ return false;
+ }
+ std::string suppressions = "--suppressions=" +
+ this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile");
+ this->MemoryTesterOptions.push_back(suppressions);
+ }
+ std::string outputFile = "--log-file=" + this->MemoryTesterOutputFile;
+ this->MemoryTesterDynamicOptions.push_back(outputFile);
+ break;
+ }
+ case cmCTestMemCheckHandler::PURIFY: {
+ std::string outputFile;
+#ifdef _WIN32
+ if (this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
+ .size()) {
+ if (!cmSystemTools::FileExists(
+ this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
+ .c_str())) {
+ cmCTestLog(
+ this->CTest, ERROR_MESSAGE,
+ "Cannot find memory checker suppression file: "
+ << this->CTest
+ ->GetCTestConfiguration("MemoryCheckSuppressionFile")
+ .c_str()
+ << std::endl);
+ return false;
+ }
+ std::string filterFiles = "/FilterFiles=" +
+ this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile");
+ this->MemoryTesterOptions.push_back(filterFiles);
+ }
+ outputFile = "/SAVETEXTDATA=";
+#else
+ outputFile = "-log-file=";
+#endif
+ outputFile += this->MemoryTesterOutputFile;
+ this->MemoryTesterDynamicOptions.push_back(outputFile);
+ break;
+ }
+ case cmCTestMemCheckHandler::BOUNDS_CHECKER: {
+ this->BoundsCheckerXMLFile = this->MemoryTesterOutputFile;
+ std::string dpbdFile = this->CTest->GetBinaryDir() +
+ "/Testing/Temporary/MemoryChecker.??.DPbd";
+ this->BoundsCheckerDPBDFile = dpbdFile;
+ this->MemoryTesterDynamicOptions.push_back("/B");
+ this->MemoryTesterDynamicOptions.push_back(dpbdFile);
+ this->MemoryTesterDynamicOptions.push_back("/X");
+ this->MemoryTesterDynamicOptions.push_back(this->MemoryTesterOutputFile);
+ this->MemoryTesterOptions.push_back("/M");
+ break;
+ }
+ // these are almost the same but the env var used is different
+ case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
+ case cmCTestMemCheckHandler::THREAD_SANITIZER:
+ case cmCTestMemCheckHandler::MEMORY_SANITIZER:
+ case cmCTestMemCheckHandler::UB_SANITIZER: {
+ // To pass arguments to ThreadSanitizer the environment variable
+ // TSAN_OPTIONS is used. This is done with the cmake -E env command.
+ // The MemoryTesterDynamicOptions is setup with the -E env
+ // Then the MemoryTesterEnvironmentVariable gets the
+ // TSAN_OPTIONS string with the log_path in it.
+ this->MemoryTesterDynamicOptions.push_back("-E");
+ this->MemoryTesterDynamicOptions.push_back("env");
+ std::string envVar;
+ std::string extraOptions =
+ this->CTest->GetCTestConfiguration("MemoryCheckSanitizerOptions");
+ if (this->MemoryTesterStyle ==
+ cmCTestMemCheckHandler::ADDRESS_SANITIZER) {
+ envVar = "ASAN_OPTIONS";
+ extraOptions += " detect_leaks=1";
+ } else if (this->MemoryTesterStyle ==
+ cmCTestMemCheckHandler::THREAD_SANITIZER) {
+ envVar = "TSAN_OPTIONS";
+ } else if (this->MemoryTesterStyle ==
+ cmCTestMemCheckHandler::MEMORY_SANITIZER) {
+ envVar = "MSAN_OPTIONS";
+ } else if (this->MemoryTesterStyle ==
+ cmCTestMemCheckHandler::UB_SANITIZER) {
+ envVar = "UBSAN_OPTIONS";
+ }
+ std::string outputFile =
+ envVar + "=log_path=\"" + this->MemoryTesterOutputFile + "\" ";
+ this->MemoryTesterEnvironmentVariable = outputFile + extraOptions;
+ break;
+ }
+ default:
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Do not understand memory checker: " << this->MemoryTester
+ << std::endl);
+ return false;
+ }
+
+ this->InitializeResultsVectors();
+ // std::vector<std::string>::size_type cc;
+ // for ( cc = 0; cmCTestMemCheckResultStrings[cc]; cc ++ )
+ // {
+ // this->MemoryTesterGlobalResults[cc] = 0;
+ // }
+ return true;
+}
+
+bool cmCTestMemCheckHandler::ProcessMemCheckOutput(const std::string& str,
+ std::string& log,
+ std::vector<int>& results)
+{
+ switch (this->MemoryTesterStyle) {
+ case cmCTestMemCheckHandler::VALGRIND:
+ return this->ProcessMemCheckValgrindOutput(str, log, results);
+ case cmCTestMemCheckHandler::PURIFY:
+ return this->ProcessMemCheckPurifyOutput(str, log, results);
+ case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
+ case cmCTestMemCheckHandler::THREAD_SANITIZER:
+ case cmCTestMemCheckHandler::MEMORY_SANITIZER:
+ case cmCTestMemCheckHandler::UB_SANITIZER:
+ return this->ProcessMemCheckSanitizerOutput(str, log, results);
+ case cmCTestMemCheckHandler::BOUNDS_CHECKER:
+ return this->ProcessMemCheckBoundsCheckerOutput(str, log, results);
+ default:
+ log.append("\nMemory checking style used was: ");
+ log.append("None that I know");
+ log = str;
+ return true;
+ }
+}
+
+std::vector<int>::size_type cmCTestMemCheckHandler::FindOrAddWarning(
+ const std::string& warning)
+{
+ for (std::vector<std::string>::size_type i = 0;
+ i < this->ResultStrings.size(); ++i) {
+ if (this->ResultStrings[i] == warning) {
+ return i;
+ }
+ }
+ this->GlobalResults.push_back(0); // this must stay the same size
+ this->ResultStrings.push_back(warning);
+ this->ResultStringsLong.push_back(warning);
+ return this->ResultStrings.size() - 1;
+}
+bool cmCTestMemCheckHandler::ProcessMemCheckSanitizerOutput(
+ const std::string& str, std::string& log, std::vector<int>& result)
+{
+ std::string regex;
+ switch (this->MemoryTesterStyle) {
+ case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
+ regex = "ERROR: AddressSanitizer: (.*) on.*";
+ break;
+ case cmCTestMemCheckHandler::THREAD_SANITIZER:
+ regex = "WARNING: ThreadSanitizer: (.*) \\(pid=.*\\)";
+ break;
+ case cmCTestMemCheckHandler::MEMORY_SANITIZER:
+ regex = "WARNING: MemorySanitizer: (.*)";
+ break;
+ case cmCTestMemCheckHandler::UB_SANITIZER:
+ regex = "runtime error: (.*)";
+ break;
+ default:
+ break;
+ }
+ cmsys::RegularExpression sanitizerWarning(regex);
+ cmsys::RegularExpression leakWarning("(Direct|Indirect) leak of .*");
+ int defects = 0;
+ std::vector<std::string> lines;
+ cmSystemTools::Split(str.c_str(), lines);
+ std::ostringstream ostr;
+ log = "";
+ for (std::vector<std::string>::iterator i = lines.begin(); i != lines.end();
+ ++i) {
+ std::string resultFound;
+ if (leakWarning.find(*i)) {
+ resultFound = leakWarning.match(1) + " leak";
+ } else if (sanitizerWarning.find(*i)) {
+ resultFound = sanitizerWarning.match(1);
+ }
+ if (!resultFound.empty()) {
+ std::vector<int>::size_type idx = this->FindOrAddWarning(resultFound);
+ if (result.empty() || idx > result.size() - 1) {
+ result.push_back(1);
+ } else {
+ result[idx]++;
+ }
+ defects++;
+ ostr << "<b>" << this->ResultStrings[idx] << "</b> ";
+ }
+ ostr << *i << std::endl;
+ }
+ log = ostr.str();
+ return defects == 0;
+}
+bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput(
+ const std::string& str, std::string& log, std::vector<int>& results)
+{
+ std::vector<std::string> lines;
+ cmSystemTools::Split(str.c_str(), lines);
+ std::ostringstream ostr;
+ log = "";
+
+ cmsys::RegularExpression pfW("^\\[[WEI]\\] ([A-Z][A-Z][A-Z][A-Z]*): ");
+
+ int defects = 0;
+
+ for (std::vector<std::string>::iterator i = lines.begin(); i != lines.end();
+ ++i) {
+ std::vector<int>::size_type failure = this->ResultStrings.size();
+ if (pfW.find(*i)) {
+ std::vector<int>::size_type cc;
+ for (cc = 0; cc < this->ResultStrings.size(); cc++) {
+ if (pfW.match(1) == this->ResultStrings[cc]) {
+ failure = cc;
+ break;
+ }
+ }
+ if (cc == this->ResultStrings.size()) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown Purify memory fault: "
+ << pfW.match(1) << std::endl);
+ ostr << "*** Unknown Purify memory fault: " << pfW.match(1)
+ << std::endl;
+ }
+ }
+ if (failure != this->ResultStrings.size()) {
+ ostr << "<b>" << this->ResultStrings[failure] << "</b> ";
+ results[failure]++;
+ defects++;
+ }
+ ostr << *i << std::endl;
+ }
+
+ log = ostr.str();
+ return defects == 0;
+}
+
+bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput(
+ const std::string& str, std::string& log, std::vector<int>& results)
+{
+ std::vector<std::string> lines;
+ cmSystemTools::Split(str.c_str(), lines);
+ bool unlimitedOutput = false;
+ if (str.find("CTEST_FULL_OUTPUT") != str.npos ||
+ this->CustomMaximumFailedTestOutputSize == 0) {
+ unlimitedOutput = true;
+ }
+
+ std::string::size_type cc;
+
+ std::ostringstream ostr;
+ log = "";
+
+ int defects = 0;
+
+ cmsys::RegularExpression valgrindLine("^==[0-9][0-9]*==");
+
+ cmsys::RegularExpression vgFIM(
+ "== .*Invalid free\\(\\) / delete / delete\\[\\]");
+ cmsys::RegularExpression vgFMM(
+ "== .*Mismatched free\\(\\) / delete / delete \\[\\]");
+ cmsys::RegularExpression vgMLK1(
+ "== .*[0-9,]+ bytes in [0-9,]+ blocks are definitely lost"
+ " in loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgMLK2(
+ "== .*[0-9,]+ \\([0-9,]+ direct, [0-9,]+ indirect\\)"
+ " bytes in [0-9,]+ blocks are definitely lost"
+ " in loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgPAR(
+ "== .*Syscall param .* (contains|points to) unaddressable byte\\(s\\)");
+ cmsys::RegularExpression vgMPK1(
+ "== .*[0-9,]+ bytes in [0-9,]+ blocks are possibly lost in"
+ " loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgMPK2(
+ "== .*[0-9,]+ bytes in [0-9,]+ blocks are still reachable"
+ " in loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgUMC(
+ "== .*Conditional jump or move depends on uninitialised value\\(s\\)");
+ cmsys::RegularExpression vgUMR1(
+ "== .*Use of uninitialised value of size [0-9,]+");
+ cmsys::RegularExpression vgUMR2("== .*Invalid read of size [0-9,]+");
+ cmsys::RegularExpression vgUMR3("== .*Jump to the invalid address ");
+ cmsys::RegularExpression vgUMR4(
+ "== .*Syscall param .* contains "
+ "uninitialised or unaddressable byte\\(s\\)");
+ cmsys::RegularExpression vgUMR5("== .*Syscall param .* uninitialised");
+ cmsys::RegularExpression vgIPW("== .*Invalid write of size [0-9,]+");
+ cmsys::RegularExpression vgABR("== .*pthread_mutex_unlock: mutex is "
+ "locked by a different thread");
+ std::vector<std::string::size_type> nonValGrindOutput;
+ double sttime = cmSystemTools::GetTime();
+ cmCTestOptionalLog(this->CTest, DEBUG,
+ "Start test: " << lines.size() << std::endl, this->Quiet);
+ std::string::size_type totalOutputSize = 0;
+ for (cc = 0; cc < lines.size(); cc++) {
+ cmCTestOptionalLog(this->CTest, DEBUG,
+ "test line " << lines[cc] << std::endl, this->Quiet);
+
+ if (valgrindLine.find(lines[cc])) {
+ cmCTestOptionalLog(this->CTest, DEBUG,
+ "valgrind line " << lines[cc] << std::endl,
+ this->Quiet);
+ int failure = cmCTestMemCheckHandler::NO_MEMORY_FAULT;
+ if (vgFIM.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::FIM;
+ } else if (vgFMM.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::FMM;
+ } else if (vgMLK1.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::MLK;
+ } else if (vgMLK2.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::MLK;
+ } else if (vgPAR.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::PAR;
+ } else if (vgMPK1.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::MPK;
+ } else if (vgMPK2.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::MPK;
+ } else if (vgUMC.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::UMC;
+ } else if (vgUMR1.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::UMR;
+ } else if (vgUMR2.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::UMR;
+ } else if (vgUMR3.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::UMR;
+ } else if (vgUMR4.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::UMR;
+ } else if (vgUMR5.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::UMR;
+ } else if (vgIPW.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::IPW;
+ } else if (vgABR.find(lines[cc])) {
+ failure = cmCTestMemCheckHandler::ABR;
+ }
+
+ if (failure != cmCTestMemCheckHandler::NO_MEMORY_FAULT) {
+ ostr << "<b>" << this->ResultStrings[failure] << "</b> ";
+ results[failure]++;
+ defects++;
+ }
+ totalOutputSize += lines[cc].size();
+ ostr << lines[cc] << std::endl;
+ } else {
+ nonValGrindOutput.push_back(cc);
+ }
+ }
+ // Now put all all the non valgrind output into the test output
+ // This should be last in case it gets truncated by the output
+ // limiting code
+ for (std::vector<std::string::size_type>::iterator i =
+ nonValGrindOutput.begin();
+ i != nonValGrindOutput.end(); ++i) {
+ totalOutputSize += lines[*i].size();
+ ostr << lines[*i] << std::endl;
+ if (!unlimitedOutput &&
+ totalOutputSize >
+ static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)) {
+ ostr << "....\n";
+ ostr << "Test Output for this test has been truncated see testing"
+ " machine logs for full output,\n";
+ ostr << "or put CTEST_FULL_OUTPUT in the output of "
+ "this test program.\n";
+ break; // stop the copy of output if we are full
+ }
+ }
+ cmCTestOptionalLog(this->CTest, DEBUG, "End test (elapsed: "
+ << (cmSystemTools::GetTime() - sttime) << std::endl,
+ this->Quiet);
+ log = ostr.str();
+ return defects == 0;
+}
+
+bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput(
+ const std::string& str, std::string& log, std::vector<int>& results)
+{
+ log = "";
+ double sttime = cmSystemTools::GetTime();
+ std::vector<std::string> lines;
+ cmSystemTools::Split(str.c_str(), lines);
+ cmCTestOptionalLog(this->CTest, DEBUG,
+ "Start test: " << lines.size() << std::endl, this->Quiet);
+ std::vector<std::string>::size_type cc;
+ for (cc = 0; cc < lines.size(); cc++) {
+ if (lines[cc] == BOUNDS_CHECKER_MARKER) {
+ break;
+ }
+ }
+ cmBoundsCheckerParser parser(this->CTest);
+ parser.InitializeParser();
+ if (cc < lines.size()) {
+ for (cc++; cc < lines.size(); ++cc) {
+ std::string& theLine = lines[cc];
+ // check for command line arguments that are not escaped
+ // correctly by BC
+ if (theLine.find("TargetArgs=") != theLine.npos) {
+ // skip this because BC gets it wrong and we can't parse it
+ } else if (!parser.ParseChunk(theLine.c_str(), theLine.size())) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Error in ParseChunk: " << theLine << std::endl);
+ }
+ }
+ }
+ int defects = 0;
+ for (cc = 0; cc < parser.Errors.size(); ++cc) {
+ results[parser.Errors[cc]]++;
+ defects++;
+ }
+ cmCTestOptionalLog(this->CTest, DEBUG, "End test (elapsed: "
+ << (cmSystemTools::GetTime() - sttime) << std::endl,
+ this->Quiet);
+ if (defects) {
+ // only put the output of Bounds Checker if there were
+ // errors or leaks detected
+ log = parser.Log;
+ return false;
+ }
+ return true;
+}
+
+// PostProcessTest memcheck results
+void cmCTestMemCheckHandler::PostProcessTest(cmCTestTestResult& res, int test)
+{
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "PostProcessTest memcheck results for : " << res.Name
+ << std::endl,
+ this->Quiet);
+ if (this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER) {
+ this->PostProcessBoundsCheckerTest(res, test);
+ } else {
+ std::vector<std::string> files;
+ this->TestOutputFileNames(test, files);
+ for (std::vector<std::string>::iterator i = files.begin();
+ i != files.end(); ++i) {
+ this->AppendMemTesterOutput(res, *i);
+ }
+ }
+}
+
+// This method puts the bounds checker output file into the output
+// for the test
+void cmCTestMemCheckHandler::PostProcessBoundsCheckerTest(
+ cmCTestTestResult& res, int test)
+{
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "PostProcessBoundsCheckerTest for : " << res.Name
+ << std::endl,
+ this->Quiet);
+ std::vector<std::string> files;
+ this->TestOutputFileNames(test, files);
+ if (files.empty()) {
+ return;
+ }
+ std::string ofile = files[0];
+ if (ofile.empty()) {
+ return;
+ }
+ // put a scope around this to close ifs so the file can be removed
+ {
+ cmsys::ifstream ifs(ofile.c_str());
+ if (!ifs) {
+ std::string log = "Cannot read memory tester output file: " + ofile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl);
+ return;
+ }
+ res.Output += BOUNDS_CHECKER_MARKER;
+ res.Output += "\n";
+ std::string line;
+ while (cmSystemTools::GetLineFromStream(ifs, line)) {
+ res.Output += line;
+ res.Output += "\n";
+ }
+ }
+ cmSystemTools::Delay(1000);
+ cmSystemTools::RemoveFile(this->BoundsCheckerDPBDFile);
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Remove: " << this->BoundsCheckerDPBDFile << std::endl,
+ this->Quiet);
+ cmSystemTools::RemoveFile(this->BoundsCheckerXMLFile);
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Remove: " << this->BoundsCheckerXMLFile << std::endl,
+ this->Quiet);
+}
+
+void cmCTestMemCheckHandler::AppendMemTesterOutput(cmCTestTestResult& res,
+ std::string const& ofile)
+{
+ if (ofile.empty()) {
+ return;
+ }
+ // put ifs in scope so file can be deleted if needed
+ {
+ cmsys::ifstream ifs(ofile.c_str());
+ if (!ifs) {
+ std::string log = "Cannot read memory tester output file: " + ofile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl);
+ return;
+ }
+ std::string line;
+ while (cmSystemTools::GetLineFromStream(ifs, line)) {
+ res.Output += line;
+ res.Output += "\n";
+ }
+ }
+ if (this->LogWithPID) {
+ cmSystemTools::RemoveFile(ofile);
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Remove: " << ofile << "\n", this->Quiet);
+ }
+}
+
+void cmCTestMemCheckHandler::TestOutputFileNames(
+ int test, std::vector<std::string>& files)
+{
+ std::string index;
+ std::ostringstream stream;
+ stream << test;
+ index = stream.str();
+ std::string ofile = this->MemoryTesterOutputFile;
+ std::string::size_type pos = ofile.find("??");
+ ofile.replace(pos, 2, index);
+ if (this->LogWithPID) {
+ ofile += ".*";
+ cmsys::Glob g;
+ g.FindFiles(ofile);
+ if (g.GetFiles().empty()) {
+ std::string log = "Cannot find memory tester output file: " + ofile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl);
+ ofile = "";
+ } else {
+ files = g.GetFiles();
+ return;
+ }
+ } else if (!cmSystemTools::FileExists(ofile.c_str())) {
+ std::string log = "Cannot find memory tester output file: " + ofile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl);
+ ofile = "";
+ }
+ files.push_back(ofile);
+}