From c958e26fd02d43a916ff297c89eee22166fe7be7 Mon Sep 17 00:00:00 2001 From: Scott Slack-Smith Date: Fri, 30 Jun 2017 17:12:56 +0100 Subject: *Silence false positive memory leaks reported by Microsoft's debug CRT* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new RAII MemoryIsNotDeallocated class that excludes memory allocations from Microsoft’s debug CRT leak detection report. We use this RAII class to silence 2 false positive leaks that are caused by memory allocations that are intentionally never deallocated. *Background* The MS debug CRT has a lightweight memory leak detection mechanism that can only detect if a memory allocation is missing a matching deallocation. Consequently, it will report a false positive leak for memory that’s intentionally never deallocated. For example, memory that’s reachable for the entire lifetime of a app. Note the MS debug CRT is always tracking memory allocations but the final memory leak report is disabled by default. As you can’t avoid paying for its cost, you may as well use it. The memory leak report can be enabled by calling the following function #ifdef _MSC_VER _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); #endif // _MSC_VER anywhere before exiting main. For example, the following are the false positive leaks reported before this change; Detected memory leaks! Dumping objects -> {750} normal block at 0x015DF938, 8 bytes long. Data: < ] > 00 F9 5D 01 00 00 00 00 {749} normal block at 0x015DEE60, 32 bytes long. Data: <` ] ` ] ` ] > 60 EE 5D 01 60 EE 5D 01 60 EE 5D 01 01 01 CD CD {748} normal block at 0x015DF900, 12 bytes long. Data: <8 ] ` ] > 38 F9 5D 01 60 EE 5D 01 00 00 00 00 {747} normal block at 0x015DA0F8, 24 bytes long. Data: < > FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00 Object dump complete. As you can see from above it’s not easy to identify the above are false positives. Consequently, if false positive leaks are not fixed or silenced, then it becomes impractical to identify real memory leaks. --- googletest/src/gtest-port.cc | 52 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/googletest/src/gtest-port.cc b/googletest/src/gtest-port.cc index d80bd80..edd115d 100644 --- a/googletest/src/gtest-port.cc +++ b/googletest/src/gtest-port.cc @@ -279,6 +279,43 @@ void Mutex::AssertHeld() { << "The current thread is not holding the mutex @" << this; } +namespace { + +// Use the RAII idiom to flag mem allocs that are intentionally never +// deallocated. The motivation is to silence the false positive mem leaks +// that are reported by the debug version of MS's CRT which can only detect +// if an alloc is missing a matching deallocation. +// Example: +// MemoryIsNotDeallocated memory_is_not_deallocated; +// critical_section_ = new CRITICAL_SECTION; +// +class MemoryIsNotDeallocated +{ +public: + MemoryIsNotDeallocated() : old_crtdbg_flag_(0) { +#ifdef _MSC_VER + old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT + // doesn't report mem leak if there's no matching deallocation. + _CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF); +#endif // _MSC_VER + } + + ~MemoryIsNotDeallocated() { +#ifdef _MSC_VER + // Restore the original _CRTDBG_ALLOC_MEM_DF flag + _CrtSetDbgFlag(old_crtdbg_flag_); +#endif // _MSC_VER + } + +private: + int old_crtdbg_flag_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(MemoryIsNotDeallocated); +}; + +} // namespace + // Initializes owner_thread_id_ and critical_section_ in static mutexes. void Mutex::ThreadSafeLazyInit() { // Dynamic mutexes are initialized in the constructor. @@ -289,7 +326,11 @@ void Mutex::ThreadSafeLazyInit() { // If critical_section_init_phase_ was 0 before the exchange, we // are the first to test it and need to perform the initialization. owner_thread_id_ = 0; - critical_section_ = new CRITICAL_SECTION; + { + // Use RAII to flag that following mem alloc is never deallocated. + MemoryIsNotDeallocated memory_is_not_deallocated; + critical_section_ = new CRITICAL_SECTION; + } ::InitializeCriticalSection(critical_section_); // Updates the critical_section_init_phase_ to 2 to signal // initialization complete. @@ -528,10 +569,17 @@ class ThreadLocalRegistryImpl { return 0; } + // Return a newly constructed ThreadIdToThreadLocals that's intentionally never deleted + static ThreadIdToThreadLocals* NewThreadIdToThreadLocals() { + // Use RAII to flag that following mem alloc is never deallocated. + MemoryIsNotDeallocated memory_is_not_deallocated; + return new ThreadIdToThreadLocals; + } + // Returns map of thread local instances. static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { mutex_.AssertHeld(); - static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals; + static ThreadIdToThreadLocals* map = NewThreadIdToThreadLocals(); return map; } -- cgit v0.12