diff options
Diffstat (limited to 'Source/CTest/cmCTestMemCheckHandler.cxx')
-rw-r--r-- | Source/CTest/cmCTestMemCheckHandler.cxx | 1300 |
1 files changed, 1300 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx new file mode 100644 index 0000000..089e84b --- /dev/null +++ b/Source/CTest/cmCTestMemCheckHandler.cxx @@ -0,0 +1,1300 @@ +/*============================================================================ + 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 "cmXMLParser.h" +#include "cmCTest.h" +#include "cmake.h" +#include "cmGeneratedFileStream.h" +#include <cmsys/Process.h> +#include <cmsys/RegularExpression.hxx> +#include <cmsys/Base64.h> +#include <cmsys/Glob.hxx> +#include <cmsys/FStream.hxx> +#include "cmMakefile.h" +#include "cmXMLSafe.h" + +#include <stdlib.h> +#include <math.h> +#include <float.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}, + {0,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) + { + if(name == "MemoryLeak" || + name == "ResourceLeak") + { + this->Errors.push_back(cmCTestMemCheckHandler::MLK); + } + else if(name == "Error" || + name == "Dangling Pointer") + { + this->ParseError(atts); + } + // Create the log + cmOStringStream ostr; + ostr << name << ":\n"; + int i = 0; + for(; atts[i] != 0; i+=2) + { + ostr << " " << cmXMLSafe(atts[i]) + << " - " << cmXMLSafe(atts[i+1]) << "\n"; + } + ostr << "\n"; + this->Log += ostr.str(); + } + void EndElement(const std::string& ) + { + } + + const char* GetAttribute(const char* name, const char** atts) + { + int i = 0; + for(; atts[i] != 0; ++i) + { + if(strcmp(name, atts[i]) == 0) + { + return atts[i+1]; + } + } + return 0; + } + 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; + cmOStringStream 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.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<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", + 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) +{ + 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()); +} + +//---------------------------------------------------------------------- +void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) +{ + if ( !this->CTest->GetProduceXML() ) + { + return; + } + this->CTest->StartXML(os, this->AppendXML); + os << "<DynamicAnalysis Checker=\""; + switch ( this->MemoryTesterStyle ) + { + case cmCTestMemCheckHandler::VALGRIND: + os << "Valgrind"; + break; + case cmCTestMemCheckHandler::PURIFY: + os << "Purify"; + break; + case cmCTestMemCheckHandler::BOUNDS_CHECKER: + os << "BoundsChecker"; + break; + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: + os << "AddressSanitizer"; + break; + case cmCTestMemCheckHandler::THREAD_SANITIZER: + os << "ThreadSanitizer"; + break; + case cmCTestMemCheckHandler::MEMORY_SANITIZER: + os << "MemorySanitizer"; + break; + case cmCTestMemCheckHandler::UB_SANITIZER: + os << "UndefinedBehaviorSanitizer"; + break; + default: + os << "Unknown"; + } + os << "\">" << std::endl; + + os << "\t<StartDateTime>" << this->StartTest << "</StartDateTime>\n" + << "\t<StartTestTime>" << this->StartTestTime << "</StartTestTime>\n" + << "\t<TestList>\n"; + 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; + os << "\t\t<Test>" << cmXMLSafe( + this->CTest->GetShortPathToFile(testPath.c_str())) + << "</Test>" << std::endl; + } + os << "\t</TestList>\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "-- Processing memory checking output: "); + 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(os, result); + os << "\t\t<Results>" << std::endl; + for(std::vector<int>::size_type kk = 0; + kk < memcheckresults.size(); ++kk) + { + if ( memcheckresults[kk] ) + { + os << "\t\t\t<Defect type=\"" << this->ResultStringsLong[kk] + << "\">" + << memcheckresults[kk] + << "</Defect>" << std::endl; + } + this->GlobalResults[kk] += memcheckresults[kk]; + } + + std::string logTag; + if(this->CTest->ShouldCompressMemCheckOutput()) + { + this->CTest->CompressString(memcheckstr); + logTag = "\t<Log compression=\"gzip\" encoding=\"base64\">\n"; + } + else + { + logTag = "\t<Log>\n"; + } + + os + << "\t\t</Results>\n" + << logTag << cmXMLSafe(memcheckstr) << std::endl + << "\t</Log>\n"; + this->WriteTestResultFooter(os, result); + if ( current < cc ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "#" << std::flush); + current += step; + } + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory checking results:" + << std::endl); + os << "\t<DefectList>" << std::endl; + 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 + cmCTestLog(this->CTest, HANDLER_OUTPUT, + this->ResultStringsLong[cc] << " - " + << this->GlobalResults[cc] << std::endl); + os << "\t\t<Defect Type=\"" << this->ResultStringsLong[cc] + << "\"/>" << std::endl; + } + } + os << "\t</DefectList>" << std::endl; + + os << "\t<EndDateTime>" << this->EndTest << "</EndDateTime>" << std::endl; + os << "\t<EndTestTime>" << this->EndTestTime + << "</EndTestTime>" << std::endl; + os << "<ElapsedMinutes>" + << static_cast<int>(this->ElapsedTestingTime/6)/10.0 + << "</ElapsedMinutes>\n"; + + os << "</DynamicAnalysis>" << 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()) ) + { + this->MemoryTester + = this->CTest->GetCTestConfiguration("MemoryCheckCommand").c_str(); + 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").c_str(); + this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY; + } + else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( + "ValgrindCommand").c_str()) ) + { + this->MemoryTester + = this->CTest->GetCTestConfiguration("ValgrindCommand").c_str(); + this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND; + } + else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( + "BoundsCheckerCommand").c_str()) ) + { + this->MemoryTester + = this->CTest->GetCTestConfiguration("BoundsCheckerCommand").c_str(); + this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER; + } + if ( this->CTest->GetCTestConfiguration("MemoryCheckType") + == "AddressSanitizer") + { + this->MemoryTester + = this->CTest->GetCTestConfiguration("CMakeCommand").c_str(); + 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").c_str(); + 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").c_str(); + 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").c_str(); + 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.size() == 0 ) + { + cmCTestLog(this->CTest, WARNING, + "Memory checker (MemoryCheckCommand) " + "not set, or cannot find the specified program." + << std::endl); + return false; + } + + // Setup the options + std::string memoryTesterOptions; + if ( this->CTest->GetCTestConfiguration( + "MemoryCheckCommandOptions").size() ) + { + memoryTesterOptions = this->CTest->GetCTestConfiguration( + "MemoryCheckCommandOptions"); + } + else if ( this->CTest->GetCTestConfiguration( + "ValgrindCommandOptions").size() ) + { + 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").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") << 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) +{ + if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::VALGRIND ) + { + return this->ProcessMemCheckValgrindOutput(str, log, results); + } + else if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::PURIFY ) + { + return this->ProcessMemCheckPurifyOutput(str, log, results); + } + else if ( this->MemoryTesterStyle == + cmCTestMemCheckHandler::ADDRESS_SANITIZER || + this->MemoryTesterStyle == + cmCTestMemCheckHandler::THREAD_SANITIZER || + this->MemoryTesterStyle == + cmCTestMemCheckHandler::MEMORY_SANITIZER || + this->MemoryTesterStyle == + cmCTestMemCheckHandler::UB_SANITIZER) + { + return this->ProcessMemCheckSanitizerOutput(str, log, results); + } + else if ( this->MemoryTesterStyle == + cmCTestMemCheckHandler::BOUNDS_CHECKER ) + { + return this->ProcessMemCheckBoundsCheckerOutput(str, log, results); + } + else + { + 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); + cmOStringStream 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.size()) + { + std::vector<int>::size_type idx = this->FindOrAddWarning(resultFound); + if(result.size() == 0 || idx > result.size()-1) + { + result.push_back(1); + } + else + { + result[idx]++; + } + defects++; + ostr << "<b>" << this->ResultStrings[idx] << "</b> "; + } + ostr << cmXMLSafe(*i) << std::endl; + } + log = ostr.str(); + if(defects) + { + return false; + } + return true; +} +//---------------------------------------------------------------------- +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); + cmOStringStream 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 << cmXMLSafe(*i) << std::endl; + } + + log = ostr.str(); + if ( defects ) + { + return false; + } + return true; +} + +//---------------------------------------------------------------------- +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; + + cmOStringStream 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(); + cmCTestLog(this->CTest, DEBUG, "Start test: " << lines.size() << std::endl); + std::string::size_type totalOutputSize = 0; + for ( cc = 0; cc < lines.size(); cc ++ ) + { + cmCTestLog(this->CTest, DEBUG, "test line " + << lines[cc] << std::endl); + + if ( valgrindLine.find(lines[cc]) ) + { + cmCTestLog(this->CTest, DEBUG, "valgrind line " + << lines[cc] << std::endl); + 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 << cmXMLSafe(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(); + cmCTestLog(this->CTest, DEBUG, "before xml safe " + << lines[*i] << std::endl); + cmCTestLog(this->CTest, DEBUG, "after xml safe " + << cmXMLSafe(lines[*i]) << std::endl); + ostr << cmXMLSafe(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 + } + } + cmCTestLog(this->CTest, DEBUG, "End test (elapsed: " + << (cmSystemTools::GetTime() - sttime) << std::endl); + log = ostr.str(); + if ( defects ) + { + return false; + } + return true; +} + + + +//---------------------------------------------------------------------- +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); + cmCTestLog(this->CTest, DEBUG, "Start test: " << lines.size() << std::endl); + 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++; + } + cmCTestLog(this->CTest, DEBUG, "End test (elapsed: " + << (cmSystemTools::GetTime() - sttime) << std::endl); + 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) +{ + 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 + { + 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) +{ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "PostProcessBoundsCheckerTest for : " + << res.Name << std::endl); + std::vector<std::string> files; + this->TestOutputFileNames(test, files); + if ( files.size() == 0 ) + { + return; + } + std::string ofile = files[0]; + // 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.c_str() << 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); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Remove: " + << this->BoundsCheckerDPBDFile << std::endl); + cmSystemTools::RemoveFile(this->BoundsCheckerXMLFile); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Remove: " + << this->BoundsCheckerXMLFile << std::endl); +} + +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.c_str() << std::endl); + return; + } + std::string line; + while ( cmSystemTools::GetLineFromStream(ifs, line) ) + { + res.Output += line; + res.Output += "\n"; + } + } + if(this->LogWithPID) + { + cmSystemTools::RemoveFile(ofile); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Remove: "<< ofile <<"\n"); + } +} + +void cmCTestMemCheckHandler::TestOutputFileNames(int test, + std::vector<std::string>& + files) +{ + std::string index; + cmOStringStream 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().size() == 0) + { + std::string log = "Cannot find memory tester output file: " + + ofile; + cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << 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.c_str() << std::endl); + ofile = ""; + } + files.push_back(ofile); +} |