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}
};
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<int>::size_type cmCTestMemCheckHandler::FindOrAddWarning(
return this->ResultStrings.size()-1;
}
//----------------------------------------------------------------------
bool cmCTestMemCheckHandler::ProcessMemCheckThreadSanitizerOutput(
bool cmCTestMemCheckHandler::ProcessMemCheckSanitizerOutput(
const std::string& str, std::string& log,
std::vector<int>& 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<std::string> lines;
cmSystemTools::Split(str.c_str(), lines);
@ -744,10 +788,18 @@ bool cmCTestMemCheckHandler::ProcessMemCheckThreadSanitizerOutput(
for( std::vector<std::string>::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<int>::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<int>::size_type idx = this->FindOrAddWarning(resultFound);
if(result.size() == 0 || idx > result.size()-1)
{
result.push_back(1);

View File

@ -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<int>& results);
bool ProcessMemCheckThreadSanitizerOutput(const std::string& str,
std::string& log,
std::vector<int>& results);
bool ProcessMemCheckSanitizerOutput(const std::string& str,
std::string& log,
std::vector<int>& results);
bool ProcessMemCheckBoundsCheckerOutput(const std::string& str,
std::string& log,
std::vector<int>& results);

View File

@ -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;
}
}

View File

@ -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;

View File

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

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