fork
Fork of cpputest by
src/CppUTest/MemoryLeakDetector.cpp
- Committer:
- Kojto
- Date:
- 2015-05-13
- Revision:
- 3:9e8c8907d9ee
- Parent:
- 1:4769360130ed
File content as of revision 3:9e8c8907d9ee:
/* * Copyright (c) 2007, Michael Feathers, James Grenning and Bas Vodde * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE EARLIER MENTIONED AUTHORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CppUTest/TestHarness.h" #include "CppUTest/MemoryLeakDetector.h" #include "CppUTest/TestMemoryAllocator.h" #include "CppUTest/PlatformSpecificFunctions.h" #define UNKNOWN ((char*)("<unknown>")) SimpleStringBuffer::SimpleStringBuffer() : positions_filled_(0), write_limit_(SIMPLE_STRING_BUFFER_LEN-1) { } void SimpleStringBuffer::clear() { positions_filled_ = 0; buffer_[0] = '\0'; } void SimpleStringBuffer::add(const char* format, ...) { int count = 0; size_t positions_left = write_limit_ - positions_filled_; if (positions_left <= 0) return; va_list arguments; va_start(arguments, format); count = PlatformSpecificVSNprintf(buffer_ + positions_filled_, positions_left+1, format, arguments); if (count > 0) positions_filled_ += (size_t) count; if (positions_filled_ > write_limit_) positions_filled_ = write_limit_; va_end(arguments); } char* SimpleStringBuffer::toString() { return buffer_; } void SimpleStringBuffer::setWriteLimit(size_t write_limit) { write_limit_ = write_limit; if (write_limit_ > SIMPLE_STRING_BUFFER_LEN-1) write_limit_ = SIMPLE_STRING_BUFFER_LEN-1; } void SimpleStringBuffer::resetWriteLimit() { write_limit_ = SIMPLE_STRING_BUFFER_LEN-1; } bool SimpleStringBuffer::reachedItsCapacity() { return positions_filled_ >= write_limit_; } //////////////////////// #define MEM_LEAK_TOO_MUCH "\netc etc etc etc. !!!! Too much memory leaks to report. Bailing out\n" #define MEM_LEAK_FOOTER "Total number of leaks: " #define MEM_LEAK_ADDITION_MALLOC_WARNING "NOTE:\n" \ "\tMemory leak reports about malloc and free can be caused by allocating using the cpputest version of malloc,\n" \ "\tbut deallocate using the standard free.\n" \ "\tIf this is the case, check whether your malloc/free replacements are working (#define malloc cpputest_malloc etc).\n" MemoryLeakOutputStringBuffer::MemoryLeakOutputStringBuffer() : total_leaks_(0), giveWarningOnUsingMalloc_(false) { } void MemoryLeakOutputStringBuffer::addAllocationLocation(const char* allocationFile, int allocationLineNumber, size_t allocationSize, TestMemoryAllocator* allocator) { outputBuffer_.add(" allocated at file: %s line: %d size: %lu type: %s\n", allocationFile, allocationLineNumber, (unsigned long) allocationSize, allocator->alloc_name()); } void MemoryLeakOutputStringBuffer::addDeallocationLocation(const char* freeFile, int freeLineNumber, TestMemoryAllocator* allocator) { outputBuffer_.add(" deallocated at file: %s line: %d type: %s\n", freeFile, freeLineNumber, allocator->free_name()); } void MemoryLeakOutputStringBuffer::addNoMemoryLeaksMessage() { outputBuffer_.add("No memory leaks were detected."); } void MemoryLeakOutputStringBuffer::startMemoryLeakReporting() { giveWarningOnUsingMalloc_ = false; total_leaks_ = 0; size_t memory_leak_normal_footer_size = sizeof(MEM_LEAK_FOOTER) + 10 + sizeof(MEM_LEAK_TOO_MUCH); /* the number of leaks */ size_t memory_leak_foot_size_with_malloc_warning = memory_leak_normal_footer_size + sizeof(MEM_LEAK_ADDITION_MALLOC_WARNING); outputBuffer_.setWriteLimit(SimpleStringBuffer::SIMPLE_STRING_BUFFER_LEN - memory_leak_foot_size_with_malloc_warning); } void MemoryLeakOutputStringBuffer::reportMemoryLeak(MemoryLeakDetectorNode* leak) { if (total_leaks_ == 0) { addMemoryLeakHeader(); } total_leaks_++; outputBuffer_.add("Alloc num (%u) Leak size: %lu Allocated at: %s and line: %d. Type: \"%s\"\n\t Memory: <%p> Content: \"%.15s\"\n", leak->number_, (unsigned long) leak->size_, leak->file_, leak->line_, leak->allocator_->alloc_name(), leak->memory_, leak->memory_); if (PlatformSpecificStrCmp(leak->allocator_->alloc_name(), "malloc") == 0) giveWarningOnUsingMalloc_ = true; } void MemoryLeakOutputStringBuffer::stopMemoryLeakReporting() { if (total_leaks_ == 0) { addNoMemoryLeaksMessage(); return; } bool buffer_reached_its_capacity = outputBuffer_.reachedItsCapacity(); outputBuffer_.resetWriteLimit(); if (buffer_reached_its_capacity) addErrorMessageForTooMuchLeaks(); addMemoryLeakFooter(total_leaks_); if (giveWarningOnUsingMalloc_) addWarningForUsingMalloc(); } void MemoryLeakOutputStringBuffer::addMemoryLeakHeader() { outputBuffer_.add("Memory leak(s) found.\n"); } void MemoryLeakOutputStringBuffer::addErrorMessageForTooMuchLeaks() { outputBuffer_.add(MEM_LEAK_TOO_MUCH); } void MemoryLeakOutputStringBuffer::addMemoryLeakFooter(int amountOfLeaks) { outputBuffer_.add("%s %d\n", MEM_LEAK_FOOTER, amountOfLeaks); } void MemoryLeakOutputStringBuffer::addWarningForUsingMalloc() { outputBuffer_.add(MEM_LEAK_ADDITION_MALLOC_WARNING); } void MemoryLeakOutputStringBuffer::reportDeallocateNonAllocatedMemoryFailure(const char* freeFile, int freeLine, TestMemoryAllocator* freeAllocator, MemoryLeakFailure* reporter) { reportFailure("Deallocating non-allocated memory\n", "<unknown>", 0, 0, NullUnknownAllocator::defaultAllocator(), freeFile, freeLine, freeAllocator, reporter); } void MemoryLeakOutputStringBuffer::reportAllocationDeallocationMismatchFailure(MemoryLeakDetectorNode* node, const char* freeFile, int freeLineNumber, TestMemoryAllocator* freeAllocator, MemoryLeakFailure* reporter) { reportFailure("Allocation/deallocation type mismatch\n", node->file_, node->line_, node->size_, node->allocator_, freeFile, freeLineNumber, freeAllocator, reporter); } void MemoryLeakOutputStringBuffer::reportMemoryCorruptionFailure(MemoryLeakDetectorNode* node, const char* freeFile, int freeLineNumber, TestMemoryAllocator* freeAllocator, MemoryLeakFailure* reporter) { reportFailure("Memory corruption (written out of bounds?)\n", node->file_, node->line_, node->size_, node->allocator_, freeFile, freeLineNumber, freeAllocator, reporter); } void MemoryLeakOutputStringBuffer::reportFailure(const char* message, const char* allocFile, int allocLine, size_t allocSize, TestMemoryAllocator* allocAllocator, const char* freeFile, int freeLine, TestMemoryAllocator* freeAllocator, MemoryLeakFailure* reporter) { outputBuffer_.add("%s", message); addAllocationLocation(allocFile, allocLine, allocSize, allocAllocator); addDeallocationLocation(freeFile, freeLine, freeAllocator); reporter->fail(toString()); } char* MemoryLeakOutputStringBuffer::toString() { return outputBuffer_.toString(); } void MemoryLeakOutputStringBuffer::clear() { outputBuffer_.clear(); } //////////////////////// void MemoryLeakDetectorNode::init(char* memory, unsigned number, size_t size, TestMemoryAllocator* allocator, MemLeakPeriod period, const char* file, int line) { number_ = number; memory_ = memory; size_ = size; allocator_ = allocator; period_ = period; file_ = file; line_ = line; } /////////////////////// bool MemoryLeakDetectorList::isInPeriod(MemoryLeakDetectorNode* node, MemLeakPeriod period) { return period == mem_leak_period_all || node->period_ == period || (node->period_ != mem_leak_period_disabled && period == mem_leak_period_enabled); } void MemoryLeakDetectorList::clearAllAccounting(MemLeakPeriod period) { MemoryLeakDetectorNode* cur = head_; MemoryLeakDetectorNode* prev = 0; while (cur) { if (isInPeriod(cur, period)) { if (prev) { prev->next_ = cur->next_; cur = prev; } else { head_ = cur->next_; cur = head_; continue; } } prev = cur; cur = cur->next_; } } void MemoryLeakDetectorList::addNewNode(MemoryLeakDetectorNode* node) { node->next_ = head_; head_ = node; } MemoryLeakDetectorNode* MemoryLeakDetectorList::removeNode(char* memory) { MemoryLeakDetectorNode* cur = head_; MemoryLeakDetectorNode* prev = 0; while (cur) { if (cur->memory_ == memory) { if (prev) { prev->next_ = cur->next_; return cur; } else { head_ = cur->next_; return cur; } } prev = cur; cur = cur->next_; } return 0; } MemoryLeakDetectorNode* MemoryLeakDetectorList::retrieveNode(char* memory) { MemoryLeakDetectorNode* cur = head_; while (cur) { if (cur->memory_ == memory) return cur; cur = cur->next_; } return NULL; } MemoryLeakDetectorNode* MemoryLeakDetectorList::getLeakFrom(MemoryLeakDetectorNode* node, MemLeakPeriod period) { for (MemoryLeakDetectorNode* cur = node; cur; cur = cur->next_) if (isInPeriod(cur, period)) return cur; return 0; } MemoryLeakDetectorNode* MemoryLeakDetectorList::getFirstLeak(MemLeakPeriod period) { return getLeakFrom(head_, period); } MemoryLeakDetectorNode* MemoryLeakDetectorList::getNextLeak(MemoryLeakDetectorNode* node, MemLeakPeriod period) { return getLeakFrom(node->next_, period); } int MemoryLeakDetectorList::getTotalLeaks(MemLeakPeriod period) { int total_leaks = 0; for (MemoryLeakDetectorNode* node = head_; node; node = node->next_) { if (isInPeriod(node, period)) total_leaks++; } return total_leaks; } bool MemoryLeakDetectorList::hasLeaks(MemLeakPeriod period) { for (MemoryLeakDetectorNode* node = head_; node; node = node->next_) if (isInPeriod(node, period)) return true; return false; } ///////////////////////////////////////////////////////////// unsigned long MemoryLeakDetectorTable::hash(char* memory) { return (unsigned long)((size_t)memory % hash_prime); } void MemoryLeakDetectorTable::clearAllAccounting(MemLeakPeriod period) { for (int i = 0; i < hash_prime; i++) table_[i].clearAllAccounting(period); } void MemoryLeakDetectorTable::addNewNode(MemoryLeakDetectorNode* node) { table_[hash(node->memory_)].addNewNode(node); } MemoryLeakDetectorNode* MemoryLeakDetectorTable::removeNode(char* memory) { return table_[hash(memory)].removeNode(memory); } MemoryLeakDetectorNode* MemoryLeakDetectorTable::retrieveNode(char* memory) { return table_[hash(memory)].retrieveNode(memory); } bool MemoryLeakDetectorTable::hasLeaks(MemLeakPeriod period) { for (int i = 0; i < hash_prime; i++) if (table_[i].hasLeaks(period)) return true; return false; } int MemoryLeakDetectorTable::getTotalLeaks(MemLeakPeriod period) { int total_leaks = 0; for (int i = 0; i < hash_prime; i++) total_leaks += table_[i].getTotalLeaks(period); return total_leaks; } MemoryLeakDetectorNode* MemoryLeakDetectorTable::getFirstLeak(MemLeakPeriod period) { for (int i = 0; i < hash_prime; i++) { MemoryLeakDetectorNode* node = table_[i].getFirstLeak(period); if (node) return node; } return 0; } MemoryLeakDetectorNode* MemoryLeakDetectorTable::getNextLeak(MemoryLeakDetectorNode* leak, MemLeakPeriod period) { unsigned long i = hash(leak->memory_); MemoryLeakDetectorNode* node = table_[i].getNextLeak(leak, period); if (node) return node; for (++i; i < hash_prime; i++) { node = table_[i].getFirstLeak(period); if (node) return node; } return 0; } ///////////////////////////////////////////////////////////// MemoryLeakDetector::MemoryLeakDetector(MemoryLeakFailure* reporter) { doAllocationTypeChecking_ = true; allocationSequenceNumber_ = 1; current_period_ = mem_leak_period_disabled; reporter_ = reporter; outputBuffer_ = MemoryLeakOutputStringBuffer(); memoryTable_ = MemoryLeakDetectorTable(); } void MemoryLeakDetector::clearAllAccounting(MemLeakPeriod period) { memoryTable_.clearAllAccounting(period); } void MemoryLeakDetector::startChecking() { outputBuffer_.clear(); current_period_ = mem_leak_period_checking; } void MemoryLeakDetector::stopChecking() { current_period_ = mem_leak_period_enabled; } void MemoryLeakDetector::enable() { current_period_ = mem_leak_period_enabled; } void MemoryLeakDetector::disable() { current_period_ = mem_leak_period_disabled; } void MemoryLeakDetector::disableAllocationTypeChecking() { doAllocationTypeChecking_ = false; } void MemoryLeakDetector::enableAllocationTypeChecking() { doAllocationTypeChecking_ = true; } unsigned MemoryLeakDetector::getCurrentAllocationNumber() { return allocationSequenceNumber_; } static size_t calculateVoidPointerAlignedSize(size_t size) { return (sizeof(void*) - (size % sizeof(void*))) + size; } size_t MemoryLeakDetector::sizeOfMemoryWithCorruptionInfo(size_t size) { return calculateVoidPointerAlignedSize(size + memory_corruption_buffer_size); } MemoryLeakDetectorNode* MemoryLeakDetector::getNodeFromMemoryPointer(char* memory, size_t memory_size) { return (MemoryLeakDetectorNode*) (void*) (memory + sizeOfMemoryWithCorruptionInfo(memory_size)); } void MemoryLeakDetector::storeLeakInformation(MemoryLeakDetectorNode * node, char *new_memory, size_t size, TestMemoryAllocator *allocator, const char *file, int line) { node->init(new_memory, allocationSequenceNumber_++, size, allocator, current_period_, file, line); addMemoryCorruptionInformation(node->memory_ + node->size_); memoryTable_.addNewNode(node); } char* MemoryLeakDetector::reallocateMemoryAndLeakInformation(TestMemoryAllocator* allocator, char* memory, size_t size, const char* file, int line, bool allocatNodesSeperately) { char* new_memory = reallocateMemoryWithAccountingInformation(allocator, memory, size, file, line, allocatNodesSeperately); if (new_memory == NULL) return NULL; MemoryLeakDetectorNode *node = createMemoryLeakAccountingInformation(allocator, size, new_memory, allocatNodesSeperately); storeLeakInformation(node, new_memory, size, allocator, file, line); return node->memory_; } void MemoryLeakDetector::invalidateMemory(char* memory) { MemoryLeakDetectorNode* node = memoryTable_.retrieveNode(memory); if (node) PlatformSpecificMemset(memory, 0xCD, node->size_); } void MemoryLeakDetector::addMemoryCorruptionInformation(char* memory) { memory[0] = 'B'; memory[1] = 'A'; memory[2] = 'S'; } bool MemoryLeakDetector::validMemoryCorruptionInformation(char* memory) { return memory[0] == 'B' && memory[1] == 'A' && memory[2] == 'S'; } bool MemoryLeakDetector::matchingAllocation(TestMemoryAllocator *alloc_allocator, TestMemoryAllocator *free_allocator) { if (alloc_allocator == free_allocator) return true; if (!doAllocationTypeChecking_) return true; return free_allocator->isOfEqualType(alloc_allocator); } void MemoryLeakDetector::checkForCorruption(MemoryLeakDetectorNode* node, const char* file, int line, TestMemoryAllocator* allocator, bool allocateNodesSeperately) { if (!matchingAllocation(node->allocator_, allocator)) outputBuffer_.reportAllocationDeallocationMismatchFailure(node, file, line, allocator, reporter_); else if (!validMemoryCorruptionInformation(node->memory_ + node->size_)) outputBuffer_.reportMemoryCorruptionFailure(node, file, line, allocator, reporter_); else if (allocateNodesSeperately) allocator->freeMemoryLeakNode((char*) node); } char* MemoryLeakDetector::allocMemory(TestMemoryAllocator* allocator, size_t size, bool allocatNodesSeperately) { return allocMemory(allocator, size, UNKNOWN, 0, allocatNodesSeperately); } char* MemoryLeakDetector::allocateMemoryWithAccountingInformation(TestMemoryAllocator* allocator, size_t size, const char* file, int line, bool allocatNodesSeperately) { if (allocatNodesSeperately) return allocator->alloc_memory(sizeOfMemoryWithCorruptionInfo(size), file, line); else return allocator->alloc_memory(sizeOfMemoryWithCorruptionInfo(size) + sizeof(MemoryLeakDetectorNode), file, line); } char* MemoryLeakDetector::reallocateMemoryWithAccountingInformation(TestMemoryAllocator* /*allocator*/, char* memory, size_t size, const char* /*file*/, int /*line*/, bool allocatNodesSeperately) { if (allocatNodesSeperately) return (char*) PlatformSpecificRealloc(memory, sizeOfMemoryWithCorruptionInfo(size)); else return (char*) PlatformSpecificRealloc(memory, sizeOfMemoryWithCorruptionInfo(size) + sizeof(MemoryLeakDetectorNode)); } MemoryLeakDetectorNode* MemoryLeakDetector::createMemoryLeakAccountingInformation(TestMemoryAllocator* allocator, size_t size, char* memory, bool allocatNodesSeperately) { if (allocatNodesSeperately) return (MemoryLeakDetectorNode*) (void*) allocator->allocMemoryLeakNode(sizeof(MemoryLeakDetectorNode)); else return getNodeFromMemoryPointer(memory, size); } char* MemoryLeakDetector::allocMemory(TestMemoryAllocator* allocator, size_t size, const char* file, int line, bool allocatNodesSeperately) { /* With malloc, it is harder to guarantee that the allocator free is called. * This is because operator new is overloaded via linker symbols, but malloc just via #defines. * If the same allocation is used and the wrong free is called, it will deallocate the memory leak information * without the memory leak detector ever noticing it! * So, for malloc, we'll allocate the memory separately so we can detect this and give a proper error. */ char* memory = allocateMemoryWithAccountingInformation(allocator, size, file, line, allocatNodesSeperately); if (memory == NULL) return NULL; MemoryLeakDetectorNode* node = createMemoryLeakAccountingInformation(allocator, size, memory, allocatNodesSeperately); storeLeakInformation(node, memory, size, allocator, file, line); return node->memory_; } void MemoryLeakDetector::removeMemoryLeakInformationWithoutCheckingOrDeallocatingTheMemoryButDeallocatingTheAccountInformation(TestMemoryAllocator* allocator, void* memory, bool allocatNodesSeperately) { MemoryLeakDetectorNode* node = memoryTable_.removeNode((char*) memory); if (allocatNodesSeperately) allocator->freeMemoryLeakNode( (char*) node); } void MemoryLeakDetector::deallocMemory(TestMemoryAllocator* allocator, void* memory, const char* file, int line, bool allocatNodesSeperately) { if (memory == 0) return; MemoryLeakDetectorNode* node = memoryTable_.removeNode((char*) memory); if (node == NULL) { outputBuffer_.reportDeallocateNonAllocatedMemoryFailure(file, line, allocator, reporter_); return; } if (!allocator->hasBeenDestroyed()) { checkForCorruption(node, file, line, allocator, allocatNodesSeperately); allocator->free_memory((char*) memory, file, line); } } void MemoryLeakDetector::deallocMemory(TestMemoryAllocator* allocator, void* memory, bool allocatNodesSeperately) { deallocMemory(allocator, (char*) memory, UNKNOWN, 0, allocatNodesSeperately); } char* MemoryLeakDetector::reallocMemory(TestMemoryAllocator* allocator, char* memory, size_t size, const char* file, int line, bool allocatNodesSeperately) { if (memory) { MemoryLeakDetectorNode* node = memoryTable_.removeNode(memory); if (node == NULL) { outputBuffer_.reportDeallocateNonAllocatedMemoryFailure(file, line, allocator, reporter_); return NULL; } checkForCorruption(node, file, line, allocator, allocatNodesSeperately); } return reallocateMemoryAndLeakInformation(allocator, memory, size, file, line, allocatNodesSeperately); } void MemoryLeakDetector::ConstructMemoryLeakReport(MemLeakPeriod period) { MemoryLeakDetectorNode* leak = memoryTable_.getFirstLeak(period); outputBuffer_.startMemoryLeakReporting(); while (leak) { outputBuffer_.reportMemoryLeak(leak); leak = memoryTable_.getNextLeak(leak, period); } outputBuffer_.stopMemoryLeakReporting(); } const char* MemoryLeakDetector::report(MemLeakPeriod period) { outputBuffer_.clear(); ConstructMemoryLeakReport(period); return outputBuffer_.toString(); } void MemoryLeakDetector::markCheckingPeriodLeaksAsNonCheckingPeriod() { MemoryLeakDetectorNode* leak = memoryTable_.getFirstLeak(mem_leak_period_checking); while (leak) { if (leak->period_ == mem_leak_period_checking) leak->period_ = mem_leak_period_enabled; leak = memoryTable_.getNextLeak(leak, mem_leak_period_checking); } } int MemoryLeakDetector::totalMemoryLeaks(MemLeakPeriod period) { return memoryTable_.getTotalLeaks(period); }