#ifndef _DEBUG_ADAPTER_H_ #define _DEBUG_ADAPTER_H_ // Intended to be included only in "debug_allocator.h" #define DBGALLOC_NO_STACKTRACE #ifndef DBGALLOC_NO_STACKTRACE #include #endif #include #include #include #ifdef __GNUC__ #include #define TR1_NS std::tr1 #else #include #define TR1_NS boost #endif #define DBGALLOC_LOG_FILENAME "dbgalloc.log" #define DBGALLOC_REPORT_FILENAME "dbgalloc_report.log" // A struct to hold the allocation information of a single pointer value. struct AllocTag { AllocTag(const char* file, size_t line) : in_use(true), age(1) { this->file = file; this->line = line; } void Reuse(const char* file, size_t line) { in_use = true; this->file = file; this->line = line; age = IncreaseAge(age); } void Unuse(const char* file, size_t line) { in_use = false; this->file = file; this->line = line; age = IncreaseAge(age); } static size_t IncreaseAge(size_t value) { size_t result = value; ++result; // Avoid zero on roll-over if (result == 0) { result = 1; } return result; } bool in_use; const char* file; size_t line; size_t age; // incremented on both the cases; alloc and free }; inline std::ostream& operator<<(std::ostream& os, const AllocTag tag) { return os << "<" << tag.file << " line:" << tag.line << " age:" << tag.age << ">"; } // Scoped(guarded) wrapper for std::ofstream struct ScopedOutputFile { ScopedOutputFile(const char* filename, std::ios_base::openmode mode = std::ios_base::app) { stream.open(filename, mode); } ~ScopedOutputFile() { if (stream.is_open()) { stream.close(); } } std::ostream& Datetime() { char buf[24]; time_t t = ::time(NULL); ::strftime(buf, 24, "%Y-%m-%d %H:%M:%S", ::localtime(&t)); return stream << buf; } std::ofstream stream; }; template class DebugPtr; // Debug allocator adapter that extends the implementation detail specified // by the template parameter class Detail. // Note that this is definitely NOT thread-safe. template class DebugAllocatorAdapter { public: ~DebugAllocatorAdapter() {} // Static initializer. static void StaticSetUp() { GetInstance().detail_.SetUp(); // singleton instantiation } // Static finalizer. static void StaticTearDown() { DebugAllocatorAdapter& instance = GetInstance(); instance.DumpLeakReport(); instance.detail_.TearDown(); } // Allocates a memory block. static void* Alloc(size_t size) { return GetInstance().detail_.Alloc(size); } // Deallocates a memory block. static void Free(void* p) { GetInstance().detail_.Free(p); } // Marks the specified heap pointer as acquired in the given context. static size_t MarkAcquired(void* p, const char* file, size_t line, const char* context) { if (p == NULL) { return 0; } AllocMapType& alloc_map = GetInstance().alloc_map_; size_t age = 0; AllocMapType::iterator it = alloc_map.find(p); if (it == alloc_map.end()) { AllocTag tag(file, line); alloc_map.insert(AllocMapType::value_type(p, tag)); age = tag.age; } else { AllocTag& tag = it->second; if (!tag.in_use) { tag.Reuse(file, line); age = tag.age; } else { // Is the Detail insane or...? ScopedOutputFile of(DBGALLOC_LOG_FILENAME); if (of.stream.is_open()) { of.Datetime() << " [" << context << "] " << p << " " << tag << " already in use. " << "(" << file << " line:" << line << ")" << std::endl; #ifndef DBGALLOC_NO_STACKTRACE PrintStack( of.stream ); #endif } } } return age; } // Marks the specified heap pointer as released in the given context. template static T* MarkReleased(T* p, const char* file, size_t line, const char* context) { return (GetInstance().VerifyReference(p, file, line, context, true) ? p : NULL); } template static T* MarkReleased(DebugPtr& ptr, const char* file, size_t line, const char* context) { return (GetInstance().VerifyReference(ptr.Get(), file, line, context, true, true, ptr.GetAge()) ? ptr.Get() : NULL); } // Retrieves the age of the specified heap pointer. static size_t RetrieveAge(void* p) { if (p == NULL) { return 0; } AllocMapType& alloc_map = GetInstance().alloc_map_; AllocMapType::iterator it = alloc_map.find(p); if (it != alloc_map.end()) { AllocTag& tag = it->second; if (tag.in_use) { return tag.age; } } return 0; } // Verifies the specified heap pointer with its age. template static T* Verify(T* p, size_t age, const char* file = NULL, size_t line = 0) { return (GetInstance().VerifyReference(p, file, line, "ref", false, true, age) ? p : NULL); } template static T* VerifyDeletion(T* p, const char* file, size_t line, bool verify_age = false, size_t age = 0) { return (GetInstance().VerifyReference(p, file, line, "pre_delete", false, verify_age, age) ? p : NULL); } static void LogBoundaryCorruption(void* p, size_t age) { AllocMapType& alloc_map = GetInstance().alloc_map_; AllocMapType::iterator it = alloc_map.find(p); if (it != alloc_map.end()) { AllocTag& tag = it->second; ScopedOutputFile of(DBGALLOC_LOG_FILENAME); if (of.stream.is_open()) { of.Datetime() << " [boundary] " << p << " " << tag << " age header corrupted:" << " current age value " << age << std::endl; #ifndef DBGALLOC_NO_STACKTRACE PrintStack( of.stream ); #endif } } } private: // Private constructor to prohibit explicit instantiation. DebugAllocatorAdapter() {} // Returns the reference to the singleton instance. static DebugAllocatorAdapter& GetInstance() { static DebugAllocatorAdapter instance; return instance; } bool VerifyReference(void* p, const char* file, size_t line, const char* context, bool mark_released, bool verify_age = false, size_t age = 0) { if (p == NULL) { return mark_released; } AllocMapType::iterator it = alloc_map_.find(p); if (it != alloc_map_.end()) { AllocTag& tag = it->second; if (tag.in_use) { if (verify_age && tag.age != age) { ScopedOutputFile of(DBGALLOC_LOG_FILENAME); if (of.stream.is_open()) { of.Datetime() << " [" << context << "] " << p << " " << tag << " has different age with " << age << " (" << file << " line:" << line << ")" << std::endl; #ifndef DBGALLOC_NO_STACKTRACE PrintStack( of.stream ); #endif } } else { if (mark_released) { tag.Unuse(file, line); } return true; } } else { ScopedOutputFile of(DBGALLOC_LOG_FILENAME); if (of.stream.is_open()) { of.Datetime() << " [" << context << "] " << p << " " << tag << " already freed. " << "(" << file << " line:" << line << ")" << std::endl; #ifndef DBGALLOC_NO_STACKTRACE PrintStack( of.stream ); #endif } } } else { ScopedOutputFile of(DBGALLOC_LOG_FILENAME); if (of.stream.is_open()) { of.Datetime() << " [" << context << "] " << p << " is not a valid entry. " << "(" << file << " line:" << line << ")" << std::endl; #ifndef DBGALLOC_NO_STACKTRACE PrintStack( of.stream ); #endif } } return false; } // Prints out memory leak report. void DumpLeakReport() { AllocMapType::iterator it = alloc_map_.begin(), end = alloc_map_.end(); for ( ; it != end; ++it) { if (it->second.in_use) { break; } } if (it == end) { return; } ScopedOutputFile of(DBGALLOC_REPORT_FILENAME); if (!of.stream.is_open()) { return; } of.Datetime() << std::endl; for ( ; it != end; ++it) { AllocTag& tag = it->second; if (tag.in_use) { of.stream << "[leak] " << it->first << " " << tag << std::endl; } } } #ifndef DBGALLOC_NO_STACKTRACE void PrintStack( std::ostream& out ) { void* array[200]; std::size_t size; char** symbols; size = backtrace( array, 200 ); symbols = backtrace_symbols( array, size ); out << std::endl; for ( std::size_t i=0; i " << symbols[i] << std::endl; } free( symbols ); } #endif typedef TR1_NS::unordered_map AllocMapType; Detail detail_; AllocMapType alloc_map_; }; #endif // _DEBUG_ADAPTER_H_