From 44726714322ca0b75628e234229f4583a480d7ec Mon Sep 17 00:00:00 2001 From: Bill Hoffman Date: Mon, 14 Jul 2014 17:01:47 -0400 Subject: [PATCH] ctest_memcheck: Add support for memory and leak sanitizer. This adds support for memory and leak sanitizers. This is built into clang and gcc 4.8 and new compilers. It is activated with a -f switch during compile. --- Source/CTest/cmCTestMemCheckHandler.cxx | 74 ++++++++++++++++--- Source/CTest/cmCTestMemCheckHandler.h | 9 ++- Source/cmXMLParser.cxx | 13 +++- Source/cmXMLParser.h | 9 ++- Tests/CTestTestMemcheck/CMakeLists.txt | 39 +++++++++- .../testAddressSanitizer.cmake | 55 ++++++++++++++ .../CTestTestMemcheck/testLeakSanitizer.cmake | 36 +++++++++ 7 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 Tests/CTestTestMemcheck/testAddressSanitizer.cmake create mode 100644 Tests/CTestTestMemcheck/testLeakSanitizer.cmake diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx index bcf09adbc..a38935991 100644 --- a/Source/CTest/cmCTestMemCheckHandler.cxx +++ b/Source/CTest/cmCTestMemCheckHandler.cxx @@ -45,11 +45,23 @@ static CatToErrorType cmCTestMemCheckBoundsChecker[] = { {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;} + cmBoundsCheckerParser(cmCTest* c) + { + this->CTest = c; + this->SetErrorCallback(xmlReportError, (void*)c); + } void StartElement(const std::string& name, const char** atts) { if(name == "MemoryLeak" || @@ -361,6 +373,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) case cmCTestMemCheckHandler::THREAD_SANITIZER: os << "ThreadSanitizer"; break; + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: + os << "AddressSanitizer"; + break; default: os << "Unknown"; } @@ -529,6 +544,14 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() this->MemoryTesterStyle = cmCTestMemCheckHandler::THREAD_SANITIZER; this->LogWithPID = true; // even if we give the log file the pid is added } + 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 + } // Check the MemoryCheckType if(this->MemoryTesterStyle == cmCTestMemCheckHandler::UNKNOWN) { @@ -651,6 +674,9 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() this->MemoryTesterOptions.push_back("/M"); break; } + // these two are almost the same but the env var used + // is different + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: case cmCTestMemCheckHandler::THREAD_SANITIZER: { // To pass arguments to ThreadSanitizer the environment variable @@ -660,9 +686,16 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() // 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=\"" + std::string envVar = "TSAN_OPTIONS"; + std::string extraOptions; + if(this->MemoryTesterStyle == cmCTestMemCheckHandler::ADDRESS_SANITIZER) + { + envVar = "ASAN_OPTIONS"; + extraOptions = " detect_leaks=1"; + } + std::string outputFile = envVar + "=log_path=\"" + this->MemoryTesterOutputFile + "\""; - this->MemoryTesterEnvironmentVariable = outputFile; + this->MemoryTesterEnvironmentVariable = outputFile + extraOptions; break; } default: @@ -695,9 +728,11 @@ ProcessMemCheckOutput(const std::string& str, return this->ProcessMemCheckPurifyOutput(str, log, results); } else if ( this->MemoryTesterStyle == - cmCTestMemCheckHandler::THREAD_SANITIZER ) + cmCTestMemCheckHandler::THREAD_SANITIZER || + this->MemoryTesterStyle == + cmCTestMemCheckHandler::ADDRESS_SANITIZER) { - return this->ProcessMemCheckThreadSanitizerOutput(str, log, results); + return this->ProcessMemCheckSanitizerOutput(str, log, results); } else if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER ) @@ -730,12 +765,21 @@ std::vector::size_type cmCTestMemCheckHandler::FindOrAddWarning( return this->ResultStrings.size()-1; } //---------------------------------------------------------------------- -bool cmCTestMemCheckHandler::ProcessMemCheckThreadSanitizerOutput( +bool cmCTestMemCheckHandler::ProcessMemCheckSanitizerOutput( const std::string& str, std::string& log, std::vector& result) { - cmsys::RegularExpression - sanitizerWarning("WARNING: ThreadSanitizer: (.*) \\(pid=.*\\)"); + std::string regex; + if(this->MemoryTesterStyle == cmCTestMemCheckHandler::THREAD_SANITIZER) + { + regex = "WARNING: ThreadSanitizer: (.*) \\(pid=.*\\)"; + } + else + { + regex = "ERROR: AddressSanitizer: (.*) on.*"; + } + cmsys::RegularExpression sanitizerWarning(regex); + cmsys::RegularExpression leakWarning("(Direct|Indirect) leak of .*"); int defects = 0; std::vector lines; cmSystemTools::Split(str.c_str(), lines); @@ -744,10 +788,18 @@ bool cmCTestMemCheckHandler::ProcessMemCheckThreadSanitizerOutput( for( std::vector::iterator i = lines.begin(); i != lines.end(); ++i) { - if(sanitizerWarning.find(*i)) + std::string resultFound; + if(leakWarning.find(*i)) { - std::string warning = sanitizerWarning.match(1); - std::vector::size_type idx = this->FindOrAddWarning(warning); + resultFound = leakWarning.match(1)+" leak"; + } + else if (sanitizerWarning.find(*i)) + { + resultFound = sanitizerWarning.match(1); + } + if(resultFound.size()) + { + std::vector::size_type idx = this->FindOrAddWarning(resultFound); if(result.size() == 0 || idx > result.size()-1) { result.push_back(1); diff --git a/Source/CTest/cmCTestMemCheckHandler.h b/Source/CTest/cmCTestMemCheckHandler.h index ffe57f6e7..2630fde4b 100644 --- a/Source/CTest/cmCTestMemCheckHandler.h +++ b/Source/CTest/cmCTestMemCheckHandler.h @@ -50,7 +50,8 @@ private: PURIFY, BOUNDS_CHECKER, // checkers after hear do not use the standard error list - THREAD_SANITIZER + THREAD_SANITIZER, + ADDRESS_SANITIZER }; public: enum { // Memory faults @@ -132,9 +133,9 @@ private: bool ProcessMemCheckPurifyOutput(const std::string& str, std::string& log, std::vector& results); - bool ProcessMemCheckThreadSanitizerOutput(const std::string& str, - std::string& log, - std::vector& results); + bool ProcessMemCheckSanitizerOutput(const std::string& str, + std::string& log, + std::vector& results); bool ProcessMemCheckBoundsCheckerOutput(const std::string& str, std::string& log, std::vector& results); diff --git a/Source/cmXMLParser.cxx b/Source/cmXMLParser.cxx index a73fd7032..391b874d1 100644 --- a/Source/cmXMLParser.cxx +++ b/Source/cmXMLParser.cxx @@ -20,6 +20,8 @@ cmXMLParser::cmXMLParser() { this->Parser = 0; this->ParseError = 0; + this->ReportCallback = 0; + this->ReportCallbackData = 0; } //---------------------------------------------------------------------------- @@ -233,6 +235,13 @@ void cmXMLParser::ReportXmlParseError() //---------------------------------------------------------------------------- void cmXMLParser::ReportError(int line, int, const char* msg) { - std::cerr << "Error parsing XML in stream at line " - << line << ": " << msg << std::endl; + if(this->ReportCallback) + { + this->ReportCallback(line, msg, this->ReportCallbackData); + } + else + { + std::cerr << "Error parsing XML in stream at line " + << line << ": " << msg << std::endl; + } } diff --git a/Source/cmXMLParser.h b/Source/cmXMLParser.h index 84a5a7dd6..e72da66b3 100644 --- a/Source/cmXMLParser.h +++ b/Source/cmXMLParser.h @@ -50,11 +50,18 @@ public: virtual int ParseChunk(const char* inputString, std::string::size_type length); virtual int CleanupParser(); - + typedef void (*ReportFunction)(int, const char*, void*); + void SetErrorCallback(ReportFunction f, void* d) + { + this->ReportCallback = f; + this->ReportCallbackData = d; + } protected: //! This variable is true if there was a parse error while parsing in //chunks. int ParseError; + ReportFunction ReportCallback; + void* ReportCallbackData; //1 Expat parser structure. Exists only during call to Parse(). void* Parser; diff --git a/Tests/CTestTestMemcheck/CMakeLists.txt b/Tests/CTestTestMemcheck/CMakeLists.txt index f470835f3..d16d43279 100644 --- a/Tests/CTestTestMemcheck/CMakeLists.txt +++ b/Tests/CTestTestMemcheck/CMakeLists.txt @@ -113,9 +113,44 @@ set(CMAKELISTS_EXTRA_CODE -P \"${CMAKE_CURRENT_SOURCE_DIR}/testThreadSanitizer.cmake\") ") gen_mc_test_internal(DummyThreadSanitizer "" -DMEMCHECK_TYPE=ThreadSanitizer) +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.*") set(CMAKELISTS_EXTRA_CODE ) set(CTEST_EXTRA_CODE) +# add LeakSanitizer 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}/testLeakSanitizer.cmake\") +") +gen_mc_test_internal(DummyLeakSanitizer "" -DMEMCHECK_TYPE=AddressSanitizer) +set(CMAKELISTS_EXTRA_CODE ) +set(CTEST_EXTRA_CODE) +set_tests_properties(CTestTestMemcheckDummyLeakSanitizer PROPERTIES + PASS_REGULAR_EXPRESSION + ".*Memory checking results:.*Direct leak - 2.*Indirect leak - 1.*") +# add AddressSanitizer 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}/testAddressSanitizer.cmake\") +") +gen_mc_test_internal(DummyAddressSanitizer "" -DMEMCHECK_TYPE=AddressSanitizer) +set(CMAKELISTS_EXTRA_CODE ) +set(CTEST_EXTRA_CODE) +set_tests_properties(CTestTestMemcheckDummyAddressSanitizer PROPERTIES + PASS_REGULAR_EXPRESSION + ".*Memory checking results:.*heap-buffer-overflow - 1.*") + + gen_mc_test(DummyPurify "\${PSEUDO_PURIFY}") gen_mc_test(DummyValgrind "\${PSEUDO_VALGRIND}") gen_mc_test(DummyBC "\${PSEUDO_BC}") @@ -202,10 +237,6 @@ 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. diff --git a/Tests/CTestTestMemcheck/testAddressSanitizer.cmake b/Tests/CTestTestMemcheck/testAddressSanitizer.cmake new file mode 100644 index 000000000..a359e2818 --- /dev/null +++ b/Tests/CTestTestMemcheck/testAddressSanitizer.cmake @@ -0,0 +1,55 @@ +# this file simulates a program that has been built with thread sanitizer +# options + +message("ASAN_OPTIONS = [$ENV{ASAN_OPTIONS}]") +string(REGEX REPLACE ".*log_path=\"([^\"]*)\".*" "\\1" LOG_FILE "$ENV{ASAN_OPTIONS}") +message("LOG_FILE=[${LOG_FILE}]") + +# 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 + +file(APPEND "${LOG_FILE}.2343" +"================================================================= +==19278== ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60080000bffc at pc 0x4009f1 bp 0x7fff82de6520 sp 0x7fff82de6518 +WRITE of size 4 at 0x60080000bffc thread T0 + #0 0x4009f0 (/home/kitware/msan/a.out+0x4009f0) + #1 0x7f18b02c876c (/lib/x86_64-linux-gnu/libc-2.15.so+0x2176c) + #2 0x400858 (/home/kitware/msan/a.out+0x400858) +0x60080000bffc is located 4 bytes to the right of 40-byte region [0x60080000bfd0,0x60080000bff8) +allocated by thread T0 here: + #0 0x7f18b088f9ca (/usr/lib/x86_64-linux-gnu/libasan.so.0.0.0+0x119ca) + #1 0x4009a2 (/home/kitware/msan/a.out+0x4009a2) + #2 0x7f18b02c876c (/lib/x86_64-linux-gnu/libc-2.15.so+0x2176c) +Shadow bytes around the buggy address: + 0x0c017fff97a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff97b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff97c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff97d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff97e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa +=>0x0c017fff97f0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa] + 0x0c017fff9800:fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff9810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff9820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff9830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c017fff9840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa +Shadow byte legend (one shadow byte represents 8 application bytes): + Addressable: 00 + Partially addressable: 01 02 03 04 05 06 07 + Heap left redzone: fa + Heap righ redzone: fb + Freed Heap region: fd + Stack left redzone: f1 + Stack mid redzone: f2 + Stack right redzone: f3 + Stack partial redzone: f4 + Stack after return: f5 + Stack use after scope: f8 + Global redzone: f9 + Global init order: f6 + Poisoned by user: f7 + ASan internal: fe +==19278== ABORTING +") diff --git a/Tests/CTestTestMemcheck/testLeakSanitizer.cmake b/Tests/CTestTestMemcheck/testLeakSanitizer.cmake new file mode 100644 index 000000000..ca0a26402 --- /dev/null +++ b/Tests/CTestTestMemcheck/testLeakSanitizer.cmake @@ -0,0 +1,36 @@ +# this file simulates a program that has been built with thread sanitizer +# options + +message("ASAN_OPTIONS = [$ENV{ASAN_OPTIONS}]") +string(REGEX REPLACE ".*log_path=\"([^\"]*)\".*" "\\1" LOG_FILE "$ENV{ASAN_OPTIONS}") +message("LOG_FILE=[${LOG_FILE}]") + +# 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 + +file(APPEND "${LOG_FILE}.2343" +"================================================================= +==25308==ERROR: LeakSanitizer: detected memory leaks + +Direct leak of 4360 byte(s) in 1 object(s) allocated from: + #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669) + #1 0x4823b4 in main /home/kitware/msan/memcheck.cxx:12 + #2 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226 + +Direct leak of 76 byte(s) in 1 object(s) allocated from: + #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669) + #1 0x4821b8 in foo() /home/kitware/msan/memcheck.cxx:4 + #2 0x4823f2 in main /home/kitware/msan/memcheck.cxx:14 + #3 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226 + +Indirect leak of 76 byte(s) in 1 object(s) allocated from: + #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669) + #1 0x4821b8 in foo() /home/kitware/msan/memcheck.cxx:4 + #2 0x4823f2 in main /home/kitware/msan/memcheck.cxx:14 + #3 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226 + +SUMMARY: AddressSanitizer: 4436 byte(s) leaked in 2 allocation(s). +")