123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmCTestMemCheckHandler.h"
- #include "cmCTest.h"
- #include "cmDuration.h"
- #include "cmSystemTools.h"
- #include "cmXMLParser.h"
- #include "cmXMLWriter.h"
- #include "cmsys/FStream.hxx"
- #include "cmsys/Glob.hxx"
- #include "cmsys/RegularExpression.hxx"
- #include <chrono>
- #include <iostream>
- #include <sstream>
- #include <string.h>
- #include <utility>
- 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 },
- { nullptr, 0 }
- };
- static void xmlReportError(int line, const char* msg, void* data)
- {
- cmCTest* ctest = static_cast<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, c);
- }
- void StartElement(const std::string& name, const char** atts) 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] != nullptr; i += 2) {
- ostr << " " << atts[i] << " - " << atts[i + 1] << "\n";
- }
- ostr << "\n";
- this->Log += ostr.str();
- }
- void EndElement(const std::string& /*name*/) override {}
- const char* GetAttribute(const char* name, const char** atts)
- {
- int i = 0;
- for (; atts[i] != nullptr; ++i) {
- if (strcmp(name, atts[i]) == 0) {
- return atts[i + 1];
- }
- }
- return 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.clear();
- this->MemoryTesterDynamicOptions.clear();
- this->MemoryTesterOptions.clear();
- this->MemoryTesterStyle = UNKNOWN;
- this->MemoryTesterOutputFile.clear();
- this->DefectCount = 0;
- }
- 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::string index;
- std::ostringstream stream;
- std::string memcheckcommand =
- cmSystemTools::ConvertToOutputPath(this->MemoryTester);
- stream << test;
- index = stream.str();
- for (std::string arg : this->MemoryTesterDynamicOptions) {
- 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 variables.
- std::string memTesterEnvironmentVariable =
- this->MemoryTesterEnvironmentVariable;
- for (std::string const& arg : this->MemoryTesterOptions) {
- if (!memTesterEnvironmentVariable.empty()) {
- // If we are using env to pass options, append all the options to
- // this string with space separation.
- memTesterEnvironmentVariable += " " + arg;
- }
- // for regular options just add them to args and memcheckcommand
- // which is just used for display
- else {
- args.push_back(arg);
- memcheckcommand += " \"";
- memcheckcommand += arg;
- 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", 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",
- nullptr
- };
- this->GlobalResults.clear();
- for (int i = 0; cmCTestMemCheckResultStrings[i] != 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);
- }
- int cmCTestMemCheckHandler::GetDefectCount()
- {
- return this->DefectCount;
- }
- void cmCTestMemCheckHandler::GenerateDartOutput(cmXMLWriter& xml)
- {
- if (!this->CTest->GetProduceXML()) {
- return;
- }
- this->CTest->StartXML(xml, this->AppendXML);
- this->CTest->GenerateSubprojectsOutput(xml);
- 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::LEAK_SANITIZER:
- xml.Attribute("Checker", "LeakSanitizer");
- 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 (cmCTestTestResult const& result : this->TestResults) {
- 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:\n", this->Quiet);
- size_t total = this->TestResults.size();
- for (cc = 0; cc < this->TestResults.size(); cc++) {
- cmCTestTestResult const& 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");
- int memoryErrors = 0;
- 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]);
- memoryErrors += memcheckresults[kk];
- xml.EndElement(); // Defect
- }
- this->GlobalResults[kk] += memcheckresults[kk];
- }
- xml.EndElement(); // Results
- if (memoryErrors > 0) {
- const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
- std::string outname = result.Name + " ";
- outname.resize(maxTestNameWidth + 4, '.');
- cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, cc + 1
- << "/" << total << " MemCheck: #"
- << result.TestCount << ": " << outname
- << " Defects: " << memoryErrors << std::endl,
- this->Quiet);
- }
- xml.StartElement("Log");
- if (this->CTest->ShouldCompressTestOutput()) {
- this->CTest->CompressString(memcheckstr);
- xml.Attribute("compression", "gzip");
- xml.Attribute("encoding", "base64");
- }
- xml.Content(memcheckstr);
- xml.EndElement(); // Log
- this->WriteTestResultFooter(xml, result);
- }
- cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
- "MemCheck log files can be found here: "
- "( * corresponds to test number)"
- << std::endl,
- this->Quiet);
- std::string output = this->MemoryTesterOutputFile;
- cmSystemTools::ReplaceString(output, "??", "*");
- cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, 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]) {
- std::cerr.width(35);
- 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",
- std::chrono::duration_cast<std::chrono::minutes>(this->ElapsedTestingTime)
- .count());
- xml.EndElement(); // DynamicAnalysis
- this->CTest->EndXML(xml);
- }
- bool cmCTestMemCheckHandler::InitializeMemoryChecking()
- {
- this->MemoryTesterEnvironmentVariable.clear();
- this->MemoryTester.clear();
- // Setup the command
- if (cmSystemTools::FileExists(
- this->CTest->GetCTestConfiguration("MemoryCheckCommand"))) {
- 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"))) {
- this->MemoryTester = this->CTest->GetCTestConfiguration("PurifyCommand");
- this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY;
- } else if (cmSystemTools::FileExists(
- this->CTest->GetCTestConfiguration("ValgrindCommand"))) {
- this->MemoryTester = this->CTest->GetCTestConfiguration("ValgrindCommand");
- this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND;
- } else if (cmSystemTools::FileExists(
- this->CTest->GetCTestConfiguration("BoundsCheckerCommand"))) {
- 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") ==
- "LeakSanitizer") {
- this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand");
- this->MemoryTesterStyle = cmCTestMemCheckHandler::LEAK_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"))) {
- cmCTestLog(this->CTest, ERROR_MESSAGE,
- "Cannot find memory checker suppression file: "
- << this->CTest->GetCTestConfiguration(
- "MemoryCheckSuppressionFile")
- << std::endl);
- return false;
- }
- this->MemoryTesterOptions.push_back(
- "--suppressions=" +
- this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"));
- }
- this->MemoryTesterDynamicOptions.push_back("--log-file=" +
- this->MemoryTesterOutputFile);
- break;
- }
- case cmCTestMemCheckHandler::PURIFY: {
- std::string outputFile;
- #ifdef _WIN32
- if (this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
- .size()) {
- if (!cmSystemTools::FileExists(this->CTest->GetCTestConfiguration(
- "MemoryCheckSuppressionFile"))) {
- 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(std::move(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::LEAK_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;
- std::string suppressionsOption;
- if (!this->CTest->GetCTestConfiguration("MemoryCheckSanitizerOptions")
- .empty()) {
- extraOptions = ":" +
- this->CTest->GetCTestConfiguration("MemoryCheckSanitizerOptions");
- }
- if (!this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile")
- .empty()) {
- suppressionsOption = ":suppressions=" +
- this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile");
- }
- if (this->MemoryTesterStyle ==
- cmCTestMemCheckHandler::ADDRESS_SANITIZER) {
- envVar = "ASAN_OPTIONS";
- } else if (this->MemoryTesterStyle ==
- cmCTestMemCheckHandler::LEAK_SANITIZER) {
- envVar = "LSAN_OPTIONS";
- } 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";
- }
- // Quote log_path with single quotes; see
- // https://bugs.chromium.org/p/chromium/issues/detail?id=467936
- std::string outputFile =
- envVar + "=log_path='" + this->MemoryTesterOutputFile + "'";
- this->MemoryTesterEnvironmentVariable =
- outputFile + suppressionsOption + 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::LEAK_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::LEAK_SANITIZER:
- // use leakWarning regex
- 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.clear();
- for (std::string const& l : lines) {
- std::string resultFound;
- if (leakWarning.find(l)) {
- resultFound = leakWarning.match(1) + " leak";
- } else if (sanitizerWarning.find(l)) {
- 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 << l << std::endl;
- }
- log = ostr.str();
- this->DefectCount += defects;
- 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.clear();
- cmsys::RegularExpression pfW("^\\[[WEI]\\] ([A-Z][A-Z][A-Z][A-Z]*): ");
- int defects = 0;
- for (std::string const& l : lines) {
- std::vector<int>::size_type failure = this->ResultStrings.size();
- if (pfW.find(l)) {
- 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 << l << std::endl;
- }
- log = ostr.str();
- this->DefectCount += defects;
- 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") != std::string::npos ||
- this->CustomMaximumFailedTestOutputSize == 0) {
- unlimitedOutput = true;
- }
- std::string::size_type cc;
- std::ostringstream ostr;
- log.clear();
- 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;
- auto sttime = std::chrono::steady_clock::now();
- 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::string::size_type i : nonValGrindOutput) {
- 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: "
- << cmDurationTo<unsigned int>(std::chrono::steady_clock::now() - sttime)
- << "s)" << std::endl,
- this->Quiet);
- log = ostr.str();
- this->DefectCount += defects;
- return defects == 0;
- }
- bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput(
- const std::string& str, std::string& log, std::vector<int>& results)
- {
- log.clear();
- auto sttime = std::chrono::steady_clock::now();
- 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=") != std::string::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 (int err : parser.Errors) {
- results[err]++;
- defects++;
- }
- cmCTestOptionalLog(
- this->CTest, DEBUG, "End test (elapsed: "
- << cmDurationTo<unsigned int>(std::chrono::steady_clock::now() - sttime)
- << "s)" << std::endl,
- this->Quiet);
- if (defects) {
- // only put the output of Bounds Checker if there were
- // errors or leaks detected
- log = parser.Log;
- }
- this->DefectCount += defects;
- return defects == 0;
- }
- // 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::string const& f : files) {
- this->AppendMemTesterOutput(res, f);
- }
- }
- }
- // 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.clear();
- } else {
- files = g.GetFiles();
- return;
- }
- } else if (!cmSystemTools::FileExists(ofile)) {
- std::string log = "Cannot find memory tester output file: " + ofile;
- cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl);
- ofile.clear();
- }
- files.push_back(std::move(ofile));
- }
|