diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 983bf2276..05a7b334e 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -344,6 +344,7 @@ Variables for CTest /variable/CTEST_MEMORYCHECK_COMMAND /variable/CTEST_MEMORYCHECK_COMMAND_OPTIONS /variable/CTEST_MEMORYCHECK_SUPPRESSIONS_FILE + /variable/CTEST_MEMORYCHECK_TYPE /variable/CTEST_NIGHTLY_START_TIME /variable/CTEST_P4_CLIENT /variable/CTEST_P4_COMMAND diff --git a/Help/release/dev/thread-sanitizer.rst b/Help/release/dev/thread-sanitizer.rst new file mode 100644 index 000000000..f38e8e139 --- /dev/null +++ b/Help/release/dev/thread-sanitizer.rst @@ -0,0 +1,5 @@ +thread-sanitizer +---------------- + +* The :command:`ctest_memcheck` command learned to support + ``ThreadSanitizer``. diff --git a/Help/variable/CTEST_MEMORYCHECK_TYPE.rst b/Help/variable/CTEST_MEMORYCHECK_TYPE.rst new file mode 100644 index 000000000..f7875daab --- /dev/null +++ b/Help/variable/CTEST_MEMORYCHECK_TYPE.rst @@ -0,0 +1,6 @@ +CTEST_MEMORYCHECK_TYPE +------------------------- + +Specify the CTest ``MemoryCheckType`` setting +in a :manual:`ctest(1)` dashboard client script. +Valid values are Valgrind, Purify, BoundsChecker, and ThreadSanitizer. diff --git a/Source/CTest/cmCTestMemCheckCommand.cxx b/Source/CTest/cmCTestMemCheckCommand.cxx index 535c9934f..939b4dc64 100644 --- a/Source/CTest/cmCTestMemCheckCommand.cxx +++ b/Source/CTest/cmCTestMemCheckCommand.cxx @@ -20,6 +20,8 @@ cmCTestGenericHandler* cmCTestMemCheckCommand::InitializeActualHandler() cmCTestGenericHandler* handler = this->CTest->GetInitializedHandler("memcheck"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "MemoryCheckType", "CTEST_MEMORYCHECK_TYPE"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "MemoryCheckCommand", "CTEST_MEMORYCHECK_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx index 7b501743a..bcf09adbc 100644 --- a/Source/CTest/cmCTestMemCheckHandler.cxx +++ b/Source/CTest/cmCTestMemCheckHandler.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "cmMakefile.h" #include "cmXMLSafe.h" @@ -124,61 +125,8 @@ public: #define BOUNDS_CHECKER_MARKER \ "******######*****Begin BOUNDS CHECKER XML******######******" -//---------------------------------------------------------------------- -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", - 0 -}; -//---------------------------------------------------------------------- -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", - 0 -}; - //---------------------------------------------------------------------- cmCTestMemCheckHandler::cmCTestMemCheckHandler() @@ -186,12 +134,14 @@ 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 = ""; @@ -199,12 +149,6 @@ void cmCTestMemCheckHandler::Initialize() this->MemoryTesterOptions.clear(); this->MemoryTesterStyle = UNKNOWN; this->MemoryTesterOutputFile = ""; - int cc; - for ( cc = 0; cc < NO_MEMORY_FAULT; cc ++ ) - { - this->MemoryTesterGlobalResults[cc] = 0; - } - } //---------------------------------------------------------------------- @@ -249,8 +193,8 @@ void cmCTestMemCheckHandler::GenerateTestCommand( index = stream.str(); for ( pp = 0; pp < this->MemoryTesterDynamicOptions.size(); pp ++ ) { - std::string arg = this->MemoryTesterDynamicOptions[pp]; - std::string::size_type pos = arg.find("??"); + std::string arg = this->MemoryTesterDynamicOptions[pp]; + std::string::size_type pos = arg.find("??"); if (pos != std::string::npos) { arg.replace(pos, 2, index); @@ -260,17 +204,124 @@ void cmCTestMemCheckHandler::GenerateTestCommand( 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 ++ ) { - args.push_back(this->MemoryTesterOptions[pp]); - memcheckcommand += " \""; - memcheckcommand += this->MemoryTesterOptions[pp]; - memcheckcommand += "\""; + if(memTesterEnvironmentVariable.size()) + { + // 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.size()) + { + std::string::size_type pos = memTesterEnvironmentVariable.find("??"); + if (pos != std::string::npos) + { + memTesterEnvironmentVariable.replace(pos, 2, index); + } + memcheckcommand += " " + memTesterEnvironmentVariable; + args.push_back(memTesterEnvironmentVariable); } cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Memory check command: " << memcheckcommand << std::endl); } +//---------------------------------------------------------------------- +void cmCTestMemCheckHandler::InitializeResultsVectors() +{ + // fill these members +// cmsys::vector ResultStrings; +// cmsys::vector ResultStringsLong; +// cmsys::vector 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", + 0 + }; +//---------------------------------------------------------------------- + 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", + 0 + }; + this->GlobalResults.clear(); + for(int i =0; cmCTestMemCheckResultStrings[i] != 0; ++i) + { + this->ResultStrings.push_back(cmCTestMemCheckResultStrings[i]); + this->ResultStringsLong.push_back(cmCTestMemCheckResultLongStrings[i]); + this->GlobalResults.push_back(0); + } +} + //---------------------------------------------------------------------- void cmCTestMemCheckHandler::PopulateCustomVectors(cmMakefile *mf) { @@ -283,6 +334,8 @@ void cmCTestMemCheckHandler::PopulateCustomVectors(cmMakefile *mf) this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_MEMCHECK_IGNORE", this->CustomTestsIgnore); + this->CTest->SetCTestConfigurationFromCMakeVariable( + mf, "CMakeCommand", "CMAKE_COMMAND"); } //---------------------------------------------------------------------- @@ -292,7 +345,6 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) { return; } - this->CTest->StartXML(os, this->AppendXML); os << "MemoryTesterStyle ) @@ -306,6 +358,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) case cmCTestMemCheckHandler::BOUNDS_CHECKER: os << "BoundsChecker"; break; + case cmCTestMemCheckHandler::THREAD_SANITIZER: + os << "ThreadSanitizer"; + break; default: os << "Unknown"; } @@ -333,8 +388,7 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) { cmCTestTestResult *result = &this->TestResults[cc]; std::string memcheckstr; - int memcheckresults[cmCTestMemCheckHandler::NO_MEMORY_FAULT]; - int kk; + std::vector memcheckresults(this->ResultStrings.size(), 0); bool res = this->ProcessMemCheckOutput(result->Output, memcheckstr, memcheckresults); if ( res && result->Status == cmCTestMemCheckHandler::COMPLETED ) @@ -345,16 +399,17 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) static_cast(this->CustomMaximumFailedTestOutputSize)); this->WriteTestResultHeader(os, result); os << "\t\t" << std::endl; - for ( kk = 0; cmCTestMemCheckResultLongStrings[kk]; kk ++ ) + for(std::vector::size_type kk = 0; + kk < memcheckresults.size(); ++kk) { if ( memcheckresults[kk] ) { - os << "\t\t\tResultStringsLong[kk] << "\">" << memcheckresults[kk] << "" << std::endl; } - this->MemoryTesterGlobalResults[kk] += memcheckresults[kk]; + this->GlobalResults[kk] += memcheckresults[kk]; } std::string logTag; @@ -383,9 +438,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory checking results:" << std::endl); os << "\t" << std::endl; - for ( cc = 0; cmCTestMemCheckResultStrings[cc]; cc ++ ) + for ( cc = 0; cc < this->GlobalResults.size(); cc ++ ) { - if ( this->MemoryTesterGlobalResults[cc] ) + if ( this->GlobalResults[cc] ) { #ifdef cerr # undef cerr @@ -393,9 +448,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) std::cerr.width(35); #define cerr no_cerr cmCTestLog(this->CTest, HANDLER_OUTPUT, - cmCTestMemCheckResultLongStrings[cc] << " - " - << this->MemoryTesterGlobalResults[cc] << std::endl); - os << "\t\tResultStringsLong[cc] << " - " + << this->GlobalResults[cc] << std::endl); + os << "\t\tResultStringsLong[cc] << "\"/>" << std::endl; } } @@ -410,13 +465,13 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) os << "" << std::endl; this->CTest->EndXML(os); - - } //---------------------------------------------------------------------- bool cmCTestMemCheckHandler::InitializeMemoryChecking() { + this->MemoryTesterEnvironmentVariable = ""; + this->MemoryTester = ""; // Setup the command if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( "MemoryCheckCommand").c_str()) ) @@ -426,7 +481,9 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() std::string testerName = cmSystemTools::GetFilenameName(this->MemoryTester); // determine the checker type - if ( testerName.find("valgrind") != std::string::npos ) + if ( testerName.find("valgrind") != std::string::npos || + this->CTest->GetCTestConfiguration("MemoryCheckType") + == "Valgrind") { this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND; } @@ -464,12 +521,38 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() = this->CTest->GetCTestConfiguration("BoundsCheckerCommand").c_str(); this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER; } - else + if ( this->CTest->GetCTestConfiguration("MemoryCheckType") + == "ThreadSanitizer") + { + this->MemoryTester + = this->CTest->GetCTestConfiguration("CMakeCommand").c_str(); + this->MemoryTesterStyle = cmCTestMemCheckHandler::THREAD_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.size() == 0 ) { cmCTestLog(this->CTest, WARNING, - "Memory checker (MemoryCheckCommand) " - "not set, or cannot find the specified program." - << std::endl); + "Memory checker (MemoryCheckCommand) " + "not set, or cannot find the specified program." + << std::endl); return false; } @@ -568,6 +651,20 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() this->MemoryTesterOptions.push_back("/M"); break; } + case cmCTestMemCheckHandler::THREAD_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 outputFile = "TSAN_OPTIONS=log_path=\"" + + this->MemoryTesterOutputFile + "\""; + this->MemoryTesterEnvironmentVariable = outputFile; + break; + } default: cmCTestLog(this->CTest, ERROR_MESSAGE, "Do not understand memory checker: " << this->MemoryTester @@ -575,24 +672,20 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() return false; } - std::vector::size_type cc; - for ( cc = 0; cmCTestMemCheckResultStrings[cc]; cc ++ ) - { - this->MemoryTesterGlobalResults[cc] = 0; - } + this->InitializeResultsVectors(); + // std::vector::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, int* results) +bool cmCTestMemCheckHandler:: +ProcessMemCheckOutput(const std::string& str, + std::string& log, std::vector& results) { - std::string::size_type cc; - for ( cc = 0; cc < cmCTestMemCheckHandler::NO_MEMORY_FAULT; cc ++ ) - { - results[cc] = 0; - } - if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::VALGRIND ) { return this->ProcessMemCheckValgrindOutput(str, log, results); @@ -601,6 +694,11 @@ bool cmCTestMemCheckHandler::ProcessMemCheckOutput(const std::string& str, { return this->ProcessMemCheckPurifyOutput(str, log, results); } + else if ( this->MemoryTesterStyle == + cmCTestMemCheckHandler::THREAD_SANITIZER ) + { + return this->ProcessMemCheckThreadSanitizerOutput(str, log, results); + } else if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER ) { @@ -612,15 +710,68 @@ bool cmCTestMemCheckHandler::ProcessMemCheckOutput(const std::string& str, log.append("None that I know"); log = str; } - - return true; } +std::vector::size_type cmCTestMemCheckHandler::FindOrAddWarning( + const std::string& warning) +{ + for(std::vector::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::ProcessMemCheckThreadSanitizerOutput( + const std::string& str, std::string& log, + std::vector& result) +{ + cmsys::RegularExpression + sanitizerWarning("WARNING: ThreadSanitizer: (.*) \\(pid=.*\\)"); + int defects = 0; + std::vector lines; + cmSystemTools::Split(str.c_str(), lines); + cmOStringStream ostr; + log = ""; + for( std::vector::iterator i = lines.begin(); + i != lines.end(); ++i) + { + if(sanitizerWarning.find(*i)) + { + std::string warning = sanitizerWarning.match(1); + std::vector::size_type idx = this->FindOrAddWarning(warning); + if(result.size() == 0 || idx > result.size()-1) + { + result.push_back(1); + } + else + { + result[idx]++; + } + defects++; + ostr << "" << this->ResultStrings[idx] << " "; + } + ostr << cmXMLSafe(*i) << std::endl; + } + log = ostr.str(); + if(defects) + { + return false; + } + return true; +} //---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput( const std::string& str, std::string& log, - int* results) + std::vector& results) { std::vector lines; cmSystemTools::Split(str.c_str(), lines); @@ -634,19 +785,19 @@ bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput( for( std::vector::iterator i = lines.begin(); i != lines.end(); ++i) { - int failure = cmCTestMemCheckHandler::NO_MEMORY_FAULT; + std::vector::size_type failure = this->ResultStrings.size(); if ( pfW.find(*i) ) { - int cc; - for ( cc = 0; cc < cmCTestMemCheckHandler::NO_MEMORY_FAULT; cc ++ ) + std::vector::size_type cc; + for ( cc = 0; cc < this->ResultStrings.size(); cc ++ ) { - if ( pfW.match(1) == cmCTestMemCheckResultStrings[cc] ) + if ( pfW.match(1) == this->ResultStrings[cc] ) { failure = cc; break; } } - if ( cc == cmCTestMemCheckHandler::NO_MEMORY_FAULT ) + if ( cc == this->ResultStrings.size() ) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown Purify memory fault: " << pfW.match(1) << std::endl); @@ -654,9 +805,9 @@ bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput( << std::endl; } } - if ( failure != NO_MEMORY_FAULT ) + if ( failure != this->ResultStrings.size() ) { - ostr << "" << cmCTestMemCheckResultStrings[failure] << " "; + ostr << "" << this->ResultStrings[failure] << " "; results[failure] ++; defects ++; } @@ -674,7 +825,7 @@ bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput( //---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput( const std::string& str, std::string& log, - int* results) + std::vector& results) { std::vector lines; cmSystemTools::Split(str.c_str(), lines); @@ -803,7 +954,7 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput( if ( failure != cmCTestMemCheckHandler::NO_MEMORY_FAULT ) { - ostr << "" << cmCTestMemCheckResultStrings[failure] << " "; + ostr << "" << this->ResultStrings[failure] << " "; results[failure] ++; defects ++; } @@ -855,7 +1006,7 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput( //---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput( const std::string& str, std::string& log, - int* results) + std::vector& results) { log = ""; double sttime = cmSystemTools::GetTime(); @@ -909,6 +1060,26 @@ bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput( return true; } +// PostProcessTest memcheck results +void +cmCTestMemCheckHandler::PostProcessTest(cmCTestTestResult& res, + int test) +{ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "PostProcessTest memcheck results for : " + << res.Name << std::endl); + if(this->MemoryTesterStyle + == cmCTestMemCheckHandler::BOUNDS_CHECKER) + { + this->PostProcessBoundsCheckerTest(res, test); + } + else + { + this->AppendMemTesterOutput(res, test); + } +} + + // This method puts the bounds checker output file into the output // for the test void @@ -950,36 +1121,17 @@ cmCTestMemCheckHandler::PostProcessBoundsCheckerTest(cmCTestTestResult& res, << this->BoundsCheckerXMLFile << std::endl); } -void -cmCTestMemCheckHandler::PostProcessPurifyTest(cmCTestTestResult& res, - int test) -{ - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "PostProcessPurifyTest for : " - << res.Name << std::endl); - this->AppendMemTesterOutput(res, test); -} - -void -cmCTestMemCheckHandler::PostProcessValgrindTest(cmCTestTestResult& res, - int test) -{ - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "PostProcessValgrindTest for : " - << res.Name << std::endl); - this->AppendMemTesterOutput(res, test); -} - void cmCTestMemCheckHandler::AppendMemTesterOutput(cmCTestTestResult& res, int test) { std::string ofile = this->TestOutputFileName(test); - if ( ofile.empty() ) { return; } + // put ifs in scope so file can be deleted if needed + { cmsys::ifstream ifs(ofile.c_str()); if ( !ifs ) { @@ -993,6 +1145,12 @@ cmCTestMemCheckHandler::AppendMemTesterOutput(cmCTestTestResult& res, res.Output += line; res.Output += "\n"; } + } + if(this->LogWithPID) + { + cmSystemTools::RemoveFile(ofile.c_str()); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Remove: "<< ofile <<"\n"); + } } std::string @@ -1005,14 +1163,29 @@ cmCTestMemCheckHandler::TestOutputFileName(int test) std::string ofile = this->MemoryTesterOutputFile; std::string::size_type pos = ofile.find("??"); ofile.replace(pos, 2, index); - - if ( !cmSystemTools::FileExists(ofile.c_str()) ) + if(this->LogWithPID) + { + ofile += ".*"; + cmsys::Glob g; + g.FindFiles(ofile); + if(g.GetFiles().size() == 0) + { + std::string log = "Cannot find memory tester output file: " + + ofile; + cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl); + ofile = ""; + } + else + { + ofile = g.GetFiles()[0]; + } + } + else if ( !cmSystemTools::FileExists(ofile.c_str()) ) { std::string log = "Cannot find memory tester output file: " + ofile; cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl); ofile = ""; } - return ofile; } diff --git a/Source/CTest/cmCTestMemCheckHandler.h b/Source/CTest/cmCTestMemCheckHandler.h index 20a38bb0d..ffe57f6e7 100644 --- a/Source/CTest/cmCTestMemCheckHandler.h +++ b/Source/CTest/cmCTestMemCheckHandler.h @@ -15,7 +15,10 @@ #include "cmCTestTestHandler.h" +#include "cmStandardIncludes.h" #include "cmListFileCache.h" +#include +#include class cmMakefile; @@ -45,7 +48,9 @@ private: UNKNOWN = 0, VALGRIND, PURIFY, - BOUNDS_CHECKER + BOUNDS_CHECKER, + // checkers after hear do not use the standard error list + THREAD_SANITIZER }; public: enum { // Memory faults @@ -93,7 +98,17 @@ private: std::vector MemoryTesterOptions; int MemoryTesterStyle; std::string MemoryTesterOutputFile; - int MemoryTesterGlobalResults[NO_MEMORY_FAULT]; + std::string MemoryTesterEnvironmentVariable; + // these are used to store the types of errors that can show up + std::vector ResultStrings; + std::vector ResultStringsLong; + std::vector GlobalResults; + bool LogWithPID; // does log file add pid + + std::vector::size_type FindOrAddWarning(const std::string& warning); + // initialize the ResultStrings and ResultStringsLong for + // this type of checker + void InitializeResultsVectors(); ///! Initialize memory checking subsystem. bool InitializeMemoryChecking(); @@ -110,17 +125,22 @@ private: //string. After running, log holds the output and results hold the //different memmory errors. bool ProcessMemCheckOutput(const std::string& str, - std::string& log, int* results); + std::string& log, std::vector& results); bool ProcessMemCheckValgrindOutput(const std::string& str, - std::string& log, int* results); + std::string& log, + std::vector& results); bool ProcessMemCheckPurifyOutput(const std::string& str, - std::string& log, int* results); + std::string& log, + std::vector& results); + bool ProcessMemCheckThreadSanitizerOutput(const std::string& str, + std::string& log, + std::vector& results); bool ProcessMemCheckBoundsCheckerOutput(const std::string& str, - std::string& log, int* results); + std::string& log, + std::vector& results); - void PostProcessPurifyTest(cmCTestTestResult& res, int test); + void PostProcessTest(cmCTestTestResult& res, int test); void PostProcessBoundsCheckerTest(cmCTestTestResult& res, int test); - void PostProcessValgrindTest(cmCTestTestResult& res, int test); ///! append MemoryTesterOutputFile to the test log void AppendMemTesterOutput(cmCTestTestHandler::cmCTestTestResult& res, diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 385388d5d..bdd8c0247 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -392,20 +392,7 @@ void cmCTestRunTest::MemCheckPostProcess() << this->TestResult.Name << std::endl); cmCTestMemCheckHandler * handler = static_cast (this->TestHandler); - switch ( handler->MemoryTesterStyle ) - { - case cmCTestMemCheckHandler::VALGRIND: - handler->PostProcessValgrindTest(this->TestResult, this->Index); - break; - case cmCTestMemCheckHandler::PURIFY: - handler->PostProcessPurifyTest(this->TestResult, this->Index); - break; - case cmCTestMemCheckHandler::BOUNDS_CHECKER: - handler->PostProcessBoundsCheckerTest(this->TestResult, this->Index); - break; - default: - break; - } + handler->PostProcessTest(this->TestResult, this->Index); } //---------------------------------------------------------------------- diff --git a/Tests/CTestTestMemcheck/CMakeLists.txt b/Tests/CTestTestMemcheck/CMakeLists.txt index 89844637e..f470835f3 100644 --- a/Tests/CTestTestMemcheck/CMakeLists.txt +++ b/Tests/CTestTestMemcheck/CMakeLists.txt @@ -103,6 +103,19 @@ unset(CTEST_EXTRA_CONFIG) unset(CTEST_EXTRA_CODE) unset(CMAKELISTS_EXTRA_CODE) +# add ThreadSanitizer test +set(CTEST_EXTRA_CODE +"set(CTEST_MEMORYCHECK_COMMAND_OPTIONS \"report_bugs=1 history_size=5 exitcode=55\") +") + +set(CMAKELISTS_EXTRA_CODE +"add_test(NAME TestSan COMMAND \"${CMAKE_COMMAND}\" +-P \"${CMAKE_CURRENT_SOURCE_DIR}/testThreadSanitizer.cmake\") +") +gen_mc_test_internal(DummyThreadSanitizer "" -DMEMCHECK_TYPE=ThreadSanitizer) +set(CMAKELISTS_EXTRA_CODE ) +set(CTEST_EXTRA_CODE) + gen_mc_test(DummyPurify "\${PSEUDO_PURIFY}") gen_mc_test(DummyValgrind "\${PSEUDO_VALGRIND}") gen_mc_test(DummyBC "\${PSEUDO_BC}") @@ -189,6 +202,11 @@ set_tests_properties(CTestTestMemcheckDummyValgrindTwoTargets PROPERTIES PASS_REGULAR_EXPRESSION "\nMemory check project ${CTEST_ESCAPED_CMAKE_CURRENT_BINARY_DIR}/DummyValgrindTwoTargets\n.*\n *Start 1: RunCMake\n(.*\n)?Memory check command: .* \"--log-file=${CTEST_ESCAPED_CMAKE_CURRENT_BINARY_DIR}/DummyValgrindTwoTargets/Testing/Temporary/MemoryChecker.1.log\" \"-q\".*\n *Start 2: RunCMakeAgain\n(.*\n)?Memory check command: .* \"--log-file=${CTEST_ESCAPED_CMAKE_CURRENT_BINARY_DIR}/DummyValgrindTwoTargets/Testing/Temporary/MemoryChecker.2.log\" \"-q\".*\n") +set_tests_properties(CTestTestMemcheckDummyThreadSanitizer PROPERTIES + PASS_REGULAR_EXPRESSION + ".*Memory checking results:.*data race.* - 1.*data race on vptr .ctor/dtor vs virtual call. - 1.*heap-use-after-free - 1.*thread leak - 1.*destroy of a locked mutex - 1.*double lock of a mutex - 1.*unlock of an unlocked mutex .or by a wrong thread. - 1.*read lock of a write locked mutex - 1.*read unlock of a write locked mutex - 1.*signal-unsafe call inside of a signal - 1.*signal handler spoils errno - 1.*lock-order-inversion .potential deadlock. - 1.*") + + # Xcode 2.x forgets to create the output directory before linking # the individual architectures. if(CMAKE_OSX_ARCHITECTURES AND XCODE AND NOT "${XCODE_VERSION}" MATCHES "^[^12]") diff --git a/Tests/CTestTestMemcheck/test.cmake.in b/Tests/CTestTestMemcheck/test.cmake.in index 471e5a598..87195c5de 100644 --- a/Tests/CTestTestMemcheck/test.cmake.in +++ b/Tests/CTestTestMemcheck/test.cmake.in @@ -15,6 +15,7 @@ set(CTEST_COVERAGE_COMMAND "@COVERAGE_COMMAND@") set(CTEST_NOTES_FILES "${CTEST_SCRIPT_DIRECTORY}/${CTEST_SCRIPT_NAME}") set(CTEST_MEMORYCHECK_COMMAND "@CHECKER_COMMAND@") +set(CTEST_MEMORYCHECK_TYPE "${MEMCHECK_TYPE}") @CTEST_EXTRA_CODE@ diff --git a/Tests/CTestTestMemcheck/testThreadSanitizer.cmake b/Tests/CTestTestMemcheck/testThreadSanitizer.cmake new file mode 100644 index 000000000..d59193146 --- /dev/null +++ b/Tests/CTestTestMemcheck/testThreadSanitizer.cmake @@ -0,0 +1,47 @@ +# this file simulates a program that has been built with thread sanitizer +# options + +message("TSAN_OPTIONS = [$ENV{TSAN_OPTIONS}]") +string(REGEX REPLACE ".*log_path=\"([^\"]*)\".*" "\\1" LOG_FILE "$ENV{TSAN_OPTIONS}") +message("LOG_FILE=[${LOG_FILE}]") + +set(error_types + "data race" + "data race on vptr (ctor/dtor vs virtual call)" + "heap-use-after-free" + "thread leak" + "destroy of a locked mutex" + "double lock of a mutex" + "unlock of an unlocked mutex (or by a wrong thread)" + "read lock of a write locked mutex" + "read unlock of a write locked mutex" + "signal-unsafe call inside of a signal" + "signal handler spoils errno" + "lock-order-inversion (potential deadlock)" + ) + +# clear the log file +file(REMOVE "${LOG_FILE}.2343") + +# create an error of each type of thread santizer +# these names come from tsan_report.cc in llvm +foreach(error_type ${error_types} ) + + file(APPEND "${LOG_FILE}.2343" +"================== +WARNING: ThreadSanitizer: ${error_type} (pid=27978) + Write of size 4 at 0x7fe017ce906c by thread T1: + #0 Thread1 ??:0 (exe+0x000000000bb0) + #1 :0 (libtsan.so.0+0x00000001b279) + + Previous write of size 4 at 0x7fe017ce906c by main thread: + #0 main ??:0 (exe+0x000000000c3c) + + Thread T1 (tid=27979, running) created by main thread at: + #0 :0 (libtsan.so.0+0x00000001ed7b) + #1 main ??:0 (exe+0x000000000c2c) + +SUMMARY: ThreadSanitizer: ${error_type} ??:0 Thread1 +================== +") +endforeach()