Browse Source

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.
Bill Hoffman 11 years ago
parent
commit
4472671432

+ 63 - 11
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<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))
+      {
+      resultFound = leakWarning.match(1)+" leak";
+      }
+    else if (sanitizerWarning.find(*i))
+      {
+      resultFound = sanitizerWarning.match(1);
+      }
+    if(resultFound.size())
       {
-      std::string warning = sanitizerWarning.match(1);
-      std::vector<int>::size_type idx = this->FindOrAddWarning(warning);
+      std::vector<int>::size_type idx = this->FindOrAddWarning(resultFound);
       if(result.size() == 0 || idx > result.size()-1)
         {
         result.push_back(1);

+ 5 - 4
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<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);

+ 11 - 2
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;
+    }
 }

+ 8 - 1
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;

+ 35 - 4
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.

+ 55 - 0
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
+")

+ 36 - 0
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).
+")