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.
This commit is contained in:
Bill Hoffman 2014-07-14 17:01:47 -04:00 committed by Brad King
parent 49bf3e7d8d
commit 4472671432
7 changed files with 213 additions and 22 deletions

View File

@ -45,11 +45,23 @@ static CatToErrorType cmCTestMemCheckBoundsChecker[] = {
{0,0} {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 // parse the xml file containing the results of last BoundsChecker run
class cmBoundsCheckerParser : public cmXMLParser class cmBoundsCheckerParser : public cmXMLParser
{ {
public: 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) void StartElement(const std::string& name, const char** atts)
{ {
if(name == "MemoryLeak" || if(name == "MemoryLeak" ||
@ -361,6 +373,9 @@ void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os)
case cmCTestMemCheckHandler::THREAD_SANITIZER: case cmCTestMemCheckHandler::THREAD_SANITIZER:
os << "ThreadSanitizer"; os << "ThreadSanitizer";
break; break;
case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
os << "AddressSanitizer";
break;
default: default:
os << "Unknown"; os << "Unknown";
} }
@ -529,6 +544,14 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
this->MemoryTesterStyle = cmCTestMemCheckHandler::THREAD_SANITIZER; this->MemoryTesterStyle = cmCTestMemCheckHandler::THREAD_SANITIZER;
this->LogWithPID = true; // even if we give the log file the pid is added 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 // Check the MemoryCheckType
if(this->MemoryTesterStyle == cmCTestMemCheckHandler::UNKNOWN) if(this->MemoryTesterStyle == cmCTestMemCheckHandler::UNKNOWN)
{ {
@ -651,6 +674,9 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
this->MemoryTesterOptions.push_back("/M"); this->MemoryTesterOptions.push_back("/M");
break; break;
} }
// these two are almost the same but the env var used
// is different
case cmCTestMemCheckHandler::ADDRESS_SANITIZER:
case cmCTestMemCheckHandler::THREAD_SANITIZER: case cmCTestMemCheckHandler::THREAD_SANITIZER:
{ {
// To pass arguments to ThreadSanitizer the environment variable // To pass arguments to ThreadSanitizer the environment variable
@ -660,9 +686,16 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking()
// TSAN_OPTIONS string with the log_path in it. // TSAN_OPTIONS string with the log_path in it.
this->MemoryTesterDynamicOptions.push_back("-E"); this->MemoryTesterDynamicOptions.push_back("-E");
this->MemoryTesterDynamicOptions.push_back("env"); 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->MemoryTesterOutputFile + "\"";
this->MemoryTesterEnvironmentVariable = outputFile; this->MemoryTesterEnvironmentVariable = outputFile + extraOptions;
break; break;
} }
default: default:
@ -695,9 +728,11 @@ ProcessMemCheckOutput(const std::string& str,
return this->ProcessMemCheckPurifyOutput(str, log, results); return this->ProcessMemCheckPurifyOutput(str, log, results);
} }
else if ( this->MemoryTesterStyle == 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 == else if ( this->MemoryTesterStyle ==
cmCTestMemCheckHandler::BOUNDS_CHECKER ) cmCTestMemCheckHandler::BOUNDS_CHECKER )
@ -730,12 +765,21 @@ std::vector<int>::size_type cmCTestMemCheckHandler::FindOrAddWarning(
return this->ResultStrings.size()-1; return this->ResultStrings.size()-1;
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
bool cmCTestMemCheckHandler::ProcessMemCheckThreadSanitizerOutput( bool cmCTestMemCheckHandler::ProcessMemCheckSanitizerOutput(
const std::string& str, std::string& log, const std::string& str, std::string& log,
std::vector<int>& result) std::vector<int>& result)
{ {
cmsys::RegularExpression std::string regex;
sanitizerWarning("WARNING: ThreadSanitizer: (.*) \\(pid=.*\\)"); 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; int defects = 0;
std::vector<std::string> lines; std::vector<std::string> lines;
cmSystemTools::Split(str.c_str(), lines); cmSystemTools::Split(str.c_str(), lines);
@ -744,10 +788,18 @@ bool cmCTestMemCheckHandler::ProcessMemCheckThreadSanitizerOutput(
for( std::vector<std::string>::iterator i = lines.begin(); for( std::vector<std::string>::iterator i = lines.begin();
i != lines.end(); ++i) i != lines.end(); ++i)
{ {
if(sanitizerWarning.find(*i)) std::string resultFound;
if(leakWarning.find(*i))
{ {
std::string warning = sanitizerWarning.match(1); resultFound = leakWarning.match(1)+" leak";
std::vector<int>::size_type idx = this->FindOrAddWarning(warning); }
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) if(result.size() == 0 || idx > result.size()-1)
{ {
result.push_back(1); result.push_back(1);

View File

@ -50,7 +50,8 @@ private:
PURIFY, PURIFY,
BOUNDS_CHECKER, BOUNDS_CHECKER,
// checkers after hear do not use the standard error list // checkers after hear do not use the standard error list
THREAD_SANITIZER THREAD_SANITIZER,
ADDRESS_SANITIZER
}; };
public: public:
enum { // Memory faults enum { // Memory faults
@ -132,9 +133,9 @@ private:
bool ProcessMemCheckPurifyOutput(const std::string& str, bool ProcessMemCheckPurifyOutput(const std::string& str,
std::string& log, std::string& log,
std::vector<int>& results); std::vector<int>& results);
bool ProcessMemCheckThreadSanitizerOutput(const std::string& str, bool ProcessMemCheckSanitizerOutput(const std::string& str,
std::string& log, std::string& log,
std::vector<int>& results); std::vector<int>& results);
bool ProcessMemCheckBoundsCheckerOutput(const std::string& str, bool ProcessMemCheckBoundsCheckerOutput(const std::string& str,
std::string& log, std::string& log,
std::vector<int>& results); std::vector<int>& results);

View File

@ -20,6 +20,8 @@ cmXMLParser::cmXMLParser()
{ {
this->Parser = 0; this->Parser = 0;
this->ParseError = 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) void cmXMLParser::ReportError(int line, int, const char* msg)
{ {
std::cerr << "Error parsing XML in stream at line " if(this->ReportCallback)
<< line << ": " << msg << std::endl; {
this->ReportCallback(line, msg, this->ReportCallbackData);
}
else
{
std::cerr << "Error parsing XML in stream at line "
<< line << ": " << msg << std::endl;
}
} }

View File

@ -50,11 +50,18 @@ public:
virtual int ParseChunk(const char* inputString, virtual int ParseChunk(const char* inputString,
std::string::size_type length); std::string::size_type length);
virtual int CleanupParser(); virtual int CleanupParser();
typedef void (*ReportFunction)(int, const char*, void*);
void SetErrorCallback(ReportFunction f, void* d)
{
this->ReportCallback = f;
this->ReportCallbackData = d;
}
protected: protected:
//! This variable is true if there was a parse error while parsing in //! This variable is true if there was a parse error while parsing in
//chunks. //chunks.
int ParseError; int ParseError;
ReportFunction ReportCallback;
void* ReportCallbackData;
//1 Expat parser structure. Exists only during call to Parse(). //1 Expat parser structure. Exists only during call to Parse().
void* Parser; void* Parser;

View File

@ -113,9 +113,44 @@ set(CMAKELISTS_EXTRA_CODE
-P \"${CMAKE_CURRENT_SOURCE_DIR}/testThreadSanitizer.cmake\") -P \"${CMAKE_CURRENT_SOURCE_DIR}/testThreadSanitizer.cmake\")
") ")
gen_mc_test_internal(DummyThreadSanitizer "" -DMEMCHECK_TYPE=ThreadSanitizer) 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(CMAKELISTS_EXTRA_CODE )
set(CTEST_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(DummyPurify "\${PSEUDO_PURIFY}")
gen_mc_test(DummyValgrind "\${PSEUDO_VALGRIND}") gen_mc_test(DummyValgrind "\${PSEUDO_VALGRIND}")
gen_mc_test(DummyBC "\${PSEUDO_BC}") gen_mc_test(DummyBC "\${PSEUDO_BC}")
@ -202,10 +237,6 @@ set_tests_properties(CTestTestMemcheckDummyValgrindTwoTargets PROPERTIES
PASS_REGULAR_EXPRESSION 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") "\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 # Xcode 2.x forgets to create the output directory before linking
# the individual architectures. # the individual architectures.

View File

@ -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
")

View File

@ -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).
")