diff options
Diffstat (limited to 'src/debug/debug_new.cpp')
-rw-r--r-- | src/debug/debug_new.cpp | 890 |
1 files changed, 594 insertions, 296 deletions
diff --git a/src/debug/debug_new.cpp b/src/debug/debug_new.cpp index 7119ece2a..0daa66fe1 100644 --- a/src/debug/debug_new.cpp +++ b/src/debug/debug_new.cpp @@ -2,7 +2,7 @@ // vim:tabstop=4:shiftwidth=4:expandtab: /* - * Copyright (C) 2004-2008 Wu Yongwei <adah at users dot sourceforge dot net> + * Copyright (C) 2004-2016 Wu Yongwei <adah at users dot sourceforge dot net> * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any @@ -24,236 +24,283 @@ * This file is part of Stones of Nvwa: * http://sourceforge.net/projects/nvwa * - * original version changed for ManaPlus - * - * Copyright (C) 2011-2017 The ManaPlus Developers */ /** - * @file debug_new.cpp + * @file debug_new.cpp * * Implementation of debug versions of new and delete to check leakage. * - * @version 4.14, 2008/10/20 - * @author Wu Yongwei - * + * @date 2016-10-14 */ -#ifdef ENABLE_MEM_DEBUG -#include <new> -#include <assert.h> -#include <stdlib.h> -#include <string.h> -#ifdef __unix__ -#include <alloca.h> +#include <new> // std::bad_alloc/nothrow_t +#include <assert.h> // assert +#include <stdio.h> // fprintf/stderr +#include <stdlib.h> // abort +#include <string.h> // strcpy/strncpy/sprintf +#include "debug/_nvwa.h" // NVWA macros + +#if NVWA_UNIX +#include <alloca.h> // alloca #endif -#ifdef _WIN32 -#include <malloc.h> +#if NVWA_WIN32 +#include <malloc.h> // alloca #endif -#include "debug/fast_mutex.h" -#include "debug/static_assert.h" -#include "localconsts.h" +#if NVWA_LINUX || NVWA_APPLE +#include <execinfo.h> // backtrace +#endif + +#if NVWA_WINDOWS +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> // CaptureStackBackTrace +#endif -// #define DUMP_MEM_ADDRESSES 1 +#include "debug/c++11.h" // _NOEXCEPT/_NULLPTR +#include "debug/fast_mutex.h" // nvwa::fast_mutex +#include "debug/static_assert.h" // STATIC_ASSERT + +#undef _DEBUG_NEW_EMULATE_MALLOC +#undef _DEBUG_NEW_REDEFINE_NEW +/** + * Macro to indicate whether redefinition of \c new is wanted. Here it + * is defined to \c 0 to disable the redefinition of \c new. + */ +#define _DEBUG_NEW_REDEFINE_NEW 0 +#include "debug/debug_new.h" #if !_FAST_MUTEX_CHECK_INITIALIZATION && !defined(_NOTHREADS) #error "_FAST_MUTEX_CHECK_INITIALIZATION not set: check_leaks may not work" #endif /** - * @def M_DEBUG_NEW_ALIGNMENT + * @def _DEBUG_NEW_ALIGNMENT * * The alignment requirement of allocated memory blocks. It must be a * power of two. */ -#ifndef M_DEBUG_NEW_ALIGNMENT -#define M_DEBUG_NEW_ALIGNMENT 16 +#ifndef _DEBUG_NEW_ALIGNMENT +#define _DEBUG_NEW_ALIGNMENT 16 #endif /** - * @def M_DEBUG_NEW_CALLER_ADDRESS + * @def _DEBUG_NEW_CALLER_ADDRESS * - * The expression to return the caller address. #print_position will + * The expression to return the caller address. nvwa#print_position will * later on use this address to print the position information of memory * operation points. */ -#ifndef M_DEBUG_NEW_CALLER_ADDRESS +#ifndef _DEBUG_NEW_CALLER_ADDRESS #ifdef __GNUC__ -#define M_DEBUG_NEW_CALLER_ADDRESS __builtin_return_address(0) +#define _DEBUG_NEW_CALLER_ADDRESS __builtin_return_address(0) #else -#define M_DEBUG_NEW_CALLER_ADDRESS NULL +#define _DEBUG_NEW_CALLER_ADDRESS _NULLPTR #endif #endif /** - * @def M_DEBUG_NEW_ERROR_ACTION + * @def _DEBUG_NEW_ERROR_ACTION * * The action to take when an error occurs. The default behaviour is to - * call \e abort, unless \c M_DEBUG_NEW_ERROR_CRASH is defined, in which + * call \e abort, unless \c _DEBUG_NEW_ERROR_CRASH is defined, in which * case a segmentation fault will be triggered instead (which can be * useful on platforms like Windows that do not generate a core dump * when \e abort is called). */ -#ifndef M_DEBUG_NEW_ERROR_ACTION -#ifndef M_DEBUG_NEW_ERROR_CRASH -#define M_DEBUG_NEW_ERROR_ACTION abort() +#ifndef _DEBUG_NEW_ERROR_ACTION +#ifndef _DEBUG_NEW_ERROR_CRASH +#define _DEBUG_NEW_ERROR_ACTION abort() #else -#define M_DEBUG_NEW_ERROR_ACTION do { *((char*)0) = 0; abort(); } while (0) +#define _DEBUG_NEW_ERROR_ACTION do { *((char*)0) = 0; abort(); } while (0) #endif #endif /** - * @def M_DEBUG_NEW_FILENAME_LEN + * @def _DEBUG_NEW_FILENAME_LEN * * The length of file name stored if greater than zero. If it is zero, * only a const char pointer will be stored. Currently the default - * behaviour is to copy the file name, because I found that the exit - * leakage check cannot access the address of the file name sometimes - * (in my case, a core dump will occur when trying to access the file - * name in a shared library after a \c SIGINT). The current default - * value makes the size of new_ptr_list_t 64 on 32-bit platforms. + * value is non-zero (thus to copy the file name) on non-Windows + * platforms, because I once found that the exit leakage check could not + * access the address of the file name on Linux (in my case, a core dump + * occurred when check_leaks tried to access the file name in a shared + * library after a \c SIGINT). This value makes the size of + * new_ptr_list_t \c 64 on non-Windows 32-bit platforms (w/o stack + * backtrace). */ -#ifndef M_DEBUG_NEW_FILENAME_LEN -#define M_DEBUG_NEW_FILENAME_LEN 100 +#ifndef _DEBUG_NEW_FILENAME_LEN +#if NVWA_WINDOWS +#define _DEBUG_NEW_FILENAME_LEN 0 +#else +#define _DEBUG_NEW_FILENAME_LEN 44 +#endif #endif /** - * @def M_DEBUG_NEW_PROGNAME + * @def _DEBUG_NEW_PROGNAME * * The program (executable) name to be set at compile time. It is - * better to assign the full program path to #new_progname in \e main + * better to assign the full program path to nvwa#new_progname in \e main * (at run time) than to use this (compile-time) macro, but this macro * serves well as a quick hack. Note also that double quotation marks * need to be used around the program name, i.e., one should specify a - * command-line option like <code>-DM_DEBUG_NEW_PROGNAME=\"a.out\"</code> - * in \e bash, or <code>-DM_DEBUG_NEW_PROGNAME=\"a.exe\"</code> in the + * command-line option like <code>-D_DEBUG_NEW_PROGNAME=\\"a.out\"</code> + * in \e bash, or <code>-D_DEBUG_NEW_PROGNAME=\\"a.exe\"</code> in the * Windows command prompt. */ -#ifndef M_DEBUG_NEW_PROGNAME -#define M_DEBUG_NEW_PROGNAME nullptr +#ifndef _DEBUG_NEW_PROGNAME +#define _DEBUG_NEW_PROGNAME _NULLPTR +#endif + +/** + * @def _DEBUG_NEW_REMEMBER_STACK_TRACE + * + * Macro to indicate whether stack traces of allocations should be + * included in NVWA allocation information and be printed while leaks + * are reported. Useful to pinpoint leaks caused by strdup and other + * custom allocation functions. It is also very helpful in filtering + * out false positives caused by internal STL/C runtime operations. It + * is off by default because it is quite memory heavy. Set it to \c 1 + * to make all allocations have stack trace attached, or set to \c 2 to + * make only allocations that lack calling source code information (file + * and line) have the stack trace attached. + */ +#ifndef _DEBUG_NEW_REMEMBER_STACK_TRACE +#define _DEBUG_NEW_REMEMBER_STACK_TRACE 0 #endif /** - * @def M_DEBUG_NEW_STD_OPER_NEW + * @def _DEBUG_NEW_STD_OPER_NEW * * Macro to indicate whether the standard-conformant behaviour of * <code>operator new</code> is wanted. It is on by default now, but * the user may set it to \c 0 to revert to the old behaviour. */ -#ifndef M_DEBUG_NEW_STD_OPER_NEW -#define M_DEBUG_NEW_STD_OPER_NEW 1 +#ifndef _DEBUG_NEW_STD_OPER_NEW +#define _DEBUG_NEW_STD_OPER_NEW 1 #endif /** - * @def M_DEBUG_NEW_TAILCHECK + * @def _DEBUG_NEW_TAILCHECK * * Macro to indicate whether a writing-past-end check will be performed. * Define it to a positive integer as the number of padding bytes at the * end of a memory block for checking. */ -#ifndef M_DEBUG_NEW_TAILCHECK -#define M_DEBUG_NEW_TAILCHECK 0 +#ifndef _DEBUG_NEW_TAILCHECK +#define _DEBUG_NEW_TAILCHECK 0 #endif /** - * @def M_DEBUG_NEW_TAILCHECK_CHAR + * @def _DEBUG_NEW_TAILCHECK_CHAR * * Value of the padding bytes at the end of a memory block. */ -// #ifndef M_DEBUG_NEW_TAILCHECK_CHAR -// #define M_DEBUG_NEW_TAILCHECK_CHAR 0xCC -// #endif +#ifndef _DEBUG_NEW_TAILCHECK_CHAR +#define _DEBUG_NEW_TAILCHECK_CHAR 0xCC +#endif /** - * @def M_DEBUG_NEW_USE_ADDR2LINE + * @def _DEBUG_NEW_USE_ADDR2LINE * * Whether to use \e addr2line to convert a caller address to file/line * information. Defining it to a non-zero value will enable the * conversion (automatically done if GCC is detected). Defining it to * zero will disable the conversion. */ -#ifndef M_DEBUG_NEW_USE_ADDR2LINE +#ifndef _DEBUG_NEW_USE_ADDR2LINE #ifdef __GNUC__ -#define M_DEBUG_NEW_USE_ADDR2LINE 1 +#define _DEBUG_NEW_USE_ADDR2LINE 1 #else -#define M_DEBUG_NEW_USE_ADDR2LINE 0 +#define _DEBUG_NEW_USE_ADDR2LINE 0 #endif #endif #ifdef _MSC_VER -#pragma warning(disable: 4073) // #pragma init_seg(lib) used +#pragma warning(disable: 4074) // #pragma init_seg(compiler) used #pragma warning(disable: 4290) // C++ exception specification ignored -#pragma init_seg(lib) +#if _MSC_VER >= 1400 // Visual Studio 2005 or later +#pragma warning(disable: 4996) // Use the `unsafe' strncpy +#endif +#pragma init_seg(compiler) #endif -#undef M_DEBUG_NEW_EMULATE_MALLOC -#undef M_DEBUG_NEW_REDEFINE_NEW /** - * Macro to indicate whether redefinition of \c new is wanted. Here it - * is defined to \c 0 to disable the redefinition of \c new. + * Gets the aligned value of memory block size. */ -#define M_DEBUG_NEW_REDEFINE_NEW 0 -#include "debug/debug_new.h" +#define ALIGN(s) \ + (((s) + _DEBUG_NEW_ALIGNMENT - 1) & ~(_DEBUG_NEW_ALIGNMENT - 1)) + +NVWA_NAMESPACE_BEGIN /** - * Gets the aligned value of memory block size. + * The platform memory alignment. The current value works well in + * platforms I have tested: Windows XP, Windows 7 x64, and Mac OS X + * Leopard. It may be smaller than the real alignment, but must be + * bigger than \c sizeof(size_t) for it work. nvwa#debug_new_recorder + * uses it to detect misaligned pointer returned by `<code>new + * NonPODType[size]</code>'. */ -#define align(s) \ - (((s) + M_DEBUG_NEW_ALIGNMENT - 1) & ~(M_DEBUG_NEW_ALIGNMENT - 1)) +const size_t PLATFORM_MEM_ALIGNMENT = sizeof(size_t) * 2; /** * Structure to store the position information where \c new occurs. */ struct new_ptr_list_t { - new_ptr_list_t* next; - new_ptr_list_t* prev; - size_t size; + new_ptr_list_t* next; ///< Pointer to the next memory block + new_ptr_list_t* prev; ///< Pointer to the previous memory block + size_t size; ///< Size of the memory block union { -#if M_DEBUG_NEW_FILENAME_LEN == 0 - const char* file; +#if _DEBUG_NEW_FILENAME_LEN == 0 + const char* file; ///< Pointer to the file name of the caller #else - char file[M_DEBUG_NEW_FILENAME_LEN]; + char file[_DEBUG_NEW_FILENAME_LEN]; ///< File name of the caller #endif - void* addr; + void* addr; ///< Address of the caller to \e new }; - unsigned line :31; - unsigned is_array :1; - unsigned magic; - unsigned dumped; + unsigned line :31; ///< Line number of the caller; or \c 0 + unsigned is_array:1; ///< Non-zero iff <em>new[]</em> is used +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + void** stacktrace; ///< Pointer to stack trace information +#endif + unsigned magic; ///< Magic number for error detection }; /** - * Magic number for error detection. + * Definition of the constant magic number used for error detection. */ -const unsigned MAGIC = 0x4442474E; +static const unsigned DEBUG_NEW_MAGIC = 0x4442474E; /** * The extra memory allocated by <code>operator new</code>. */ -const int ALIGNED_LIST_ITEM_SIZE = align(sizeof(new_ptr_list_t)); +static const int ALIGNED_LIST_ITEM_SIZE = ALIGN(sizeof(new_ptr_list_t)); /** * List of all new'd pointers. */ -static new_ptr_list_t new_ptr_list = -{ +static new_ptr_list_t new_ptr_list = { &new_ptr_list, &new_ptr_list, 0, { -#if M_DEBUG_NEW_FILENAME_LEN == 0 - NULL +#if _DEBUG_NEW_FILENAME_LEN == 0 + _NULLPTR #else "" #endif }, 0, 0, - MAGIC, - false +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + _NULLPTR, +#endif + DEBUG_NEW_MAGIC }; /** @@ -272,8 +319,8 @@ static fast_mutex new_output_lock; static size_t total_mem_alloc = 0; /** - * Flag to control whether #check_leaks will be automatically called on - * program exit. + * Flag to control whether nvwa#check_leaks will be automatically called + * on program exit. */ bool new_autocheck_flag = true; @@ -291,15 +338,28 @@ FILE* new_output_fp = stderr; /** * Pointer to the program name. Its initial value is the macro - * #M_DEBUG_NEW_PROGNAME. You should try to assign the program path to + * #_DEBUG_NEW_PROGNAME. You should try to assign the program path to * it early in your application. Assigning <code>argv[0]</code> to it * in \e main is one way. If you use \e bash or \e ksh (or similar), * the following statement is probably what you want: * `<code>new_progname = getenv("_");</code>'. */ -const char* new_progname = M_DEBUG_NEW_PROGNAME; +const char* new_progname = _DEBUG_NEW_PROGNAME; -#if M_DEBUG_NEW_USE_ADDR2LINE +/** + * Pointer to the callback used to print the stack backtrace in case of + * a memory problem. A null value causes the default stack trace + * printing routine to be used. + */ +stacktrace_print_callback_t stacktrace_print_callback = _NULLPTR; + +/** + * Pointer to the callback used to filter out false positives from leak + * reports. A null value means the lack of filtering. + */ +leak_whitelist_callback_t leak_whitelist_callback = _NULLPTR; + +#if _DEBUG_NEW_USE_ADDR2LINE /** * Tries printing the position information from an instruction address. * This is the version that uses \e addr2line. @@ -311,7 +371,7 @@ const char* new_progname = M_DEBUG_NEW_PROGNAME; */ static bool print_position_from_addr(const void* addr) { - static const void* last_addr = nullptr; + static const void* last_addr = _NULLPTR; static char last_info[256] = ""; if (addr == last_addr) { @@ -322,32 +382,39 @@ static bool print_position_from_addr(const void* addr) } if (new_progname) { +#if NVWA_APPLE + const char addr2line_cmd[] = "atos -o "; +#else const char addr2line_cmd[] = "addr2line -e "; -#if defined(__CYGWIN__) || defined(_WIN32) +#endif + +#if NVWA_WINDOWS const int exeext_len = 4; #else const int exeext_len = 0; #endif -#if !defined(__CYGWIN__) && defined(__unix__) + +#if NVWA_UNIX && !NVWA_CYGWIN const char ignore_err[] = " 2>/dev/null"; -#elif defined(__CYGWIN__) || \ - (defined(_WIN32) && defined(WINVER) && WINVER >= 0x0500) +#elif NVWA_CYGWIN || \ + (NVWA_WIN32 && defined(WINVER) && WINVER >= 0x0500) const char ignore_err[] = " 2>nul"; #else const char ignore_err[] = ""; #endif - char* cmd = static_cast<char*>(alloca(strlen(new_progname) - + exeext_len - + sizeof addr2line_cmd - 1 - + sizeof ignore_err - 1 - + sizeof(void*) * 2 - + 4 /* SP + "0x" + null */)); + char* cmd = (char*)alloca(strlen(new_progname) + + exeext_len + + sizeof addr2line_cmd - 1 + + sizeof ignore_err - 1 + + sizeof(void*) * 2 + + 4 /* SP + "0x" + null */); strcpy(cmd, addr2line_cmd); strcpy(cmd + sizeof addr2line_cmd - 1, new_progname); size_t len = strlen(cmd); -#if defined(__CYGWIN__) || defined(_WIN32) - if (len <= 4 || (strcmp(cmd + len - 4, ".exe") != 0 - && strcmp(cmd + len - 4, ".EXE") != 0)) +#if NVWA_WINDOWS + if (len <= 4 + || (strcmp(cmd + len - 4, ".exe") != 0 && + strcmp(cmd + len - 4, ".EXE") != 0)) { strcpy(cmd + len, ".exe"); len += 4; @@ -397,11 +464,11 @@ static bool print_position_from_addr(const void*) { return false; } -#endif // M_DEBUG_NEW_USE_ADDR2LINE +#endif // _DEBUG_NEW_USE_ADDR2LINE /** * Prints the position information of a memory operation point. When \c - * M_DEBUG_NEW_USE_ADDR2LINE is defined to a non-zero value, this + * _DEBUG_NEW_USE_ADDR2LINE is defined to a non-zero value, this * function will try to convert a given caller address to file/line * information with \e addr2line. * @@ -412,39 +479,89 @@ static bool print_position_from_addr(const void*) */ static void print_position(const void* ptr, int line) { - if (line != 0) // Is file/line information present? + if (line != 0) // Is file/line information present? { - fprintf(new_output_fp, "%s:%d", static_cast<const char*>(ptr), line); + fprintf(new_output_fp, "%s:%d", (const char*)ptr, line); } - else if (ptr != nullptr) // Is caller address present? + else if (ptr != _NULLPTR) // Is caller address present? { - if (!print_position_from_addr(ptr)) // Fail to get source position? + if (!print_position_from_addr(ptr)) // Fail to get source position? fprintf(new_output_fp, "%p", ptr); } - else // No information is present + else // No information is present { fprintf(new_output_fp, "<Unknown>"); } } -#if M_DEBUG_NEW_TAILCHECK +#if _DEBUG_NEW_REMEMBER_STACK_TRACE +/** + * Prints the stack backtrace. + * + * When nvwa#stacktrace_print_callback is not null, it is used for + * printing the stacktrace items. Default implementation of call stack + * printing is very spartan—only stack frame pointers are + * printed—but even that output is still useful. Just do address + * lookup in LLDB etc. + * + * @param stacktrace pointer to the stack trace array + */ +static void print_stacktrace(void** stacktrace) +{ + if (stacktrace_print_callback == _NULLPTR) + { + fprintf(new_output_fp, "Stack backtrace:\n"); + for (size_t i = 0; stacktrace[i] != _NULLPTR; ++i) + fprintf(new_output_fp, "%p\n", stacktrace[i]); + } + else + { + stacktrace_print_callback(new_output_fp, stacktrace); + } +} +#endif + +/** + * Checks whether a leak should be ignored. Its runtime performance + * depends on the callback nvwa#leak_whitelist_callback. + * + * @param ptr pointer to a new_ptr_list_t struct + * @return \c true if the leak should be whitelisted; \c false + * otherwise + */ +static bool is_leak_whitelisted(new_ptr_list_t* ptr) +{ + if (leak_whitelist_callback == _NULLPTR) + return false; + + char const* file = ptr->line != 0 ? ptr->file : _NULLPTR; + int line = ptr->line; + void* addr = ptr->line == 0 ? ptr->addr : _NULLPTR; +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + void** stacktrace = ptr->stacktrace; +#else + void** stacktrace = _NULLPTR; +#endif + + return leak_whitelist_callback(file, line, addr, stacktrace); +} + +#if _DEBUG_NEW_TAILCHECK /** * Checks whether the padding bytes at the end of a memory block is * tampered with. * - * @param ptr pointer to a new_ptr_list_t struct - * @return \c true if the padding bytes are untouched; \c false - * otherwise + * @param ptr pointer to a new_ptr_list_t struct + * @return \c true if the padding bytes are untouched; \c false + * otherwise */ static bool check_tail(new_ptr_list_t* ptr) { - const unsigned char* const pointer = (unsigned char*)ptr + - ALIGNED_LIST_ITEM_SIZE + ptr->size; - for (int i = 0; i < M_DEBUG_NEW_TAILCHECK; ++i) - { - if (pointer[i] != M_DEBUG_NEW_TAILCHECK_CHAR) + const unsigned char* const tail_ptr = (unsigned char*)ptr + + ALIGNED_LIST_ITEM_SIZE + ptr->size; + for (int i = 0; i < _DEBUG_NEW_TAILCHECK; ++i) + if (tail_ptr[i] != _DEBUG_NEW_TAILCHECK_CHAR) return false; - } return true; } #endif @@ -456,48 +573,77 @@ static bool check_tail(new_ptr_list_t* ptr) * @param file null-terminated string of the file name * @param line line number * @param is_array boolean value whether this is an array operation - * @return pointer to the user-requested memory area; \c NULL - * if memory allocation is not successful + * @return pointer to the user-requested memory area; null if + * memory allocation is not successful */ static void* alloc_mem(size_t size, const char* file, int line, bool is_array) { assert(line >= 0); - STATIC_ASSERT((M_DEBUG_NEW_ALIGNMENT & (M_DEBUG_NEW_ALIGNMENT - 1)) == 0, +#if _DEBUG_NEW_TYPE == 1 + STATIC_ASSERT(_DEBUG_NEW_ALIGNMENT >= PLATFORM_MEM_ALIGNMENT, + Alignment_too_small); +#endif + STATIC_ASSERT((_DEBUG_NEW_ALIGNMENT & (_DEBUG_NEW_ALIGNMENT - 1)) == 0, Alignment_must_be_power_of_two); - STATIC_ASSERT(M_DEBUG_NEW_TAILCHECK >= 0, Invalid_tail_check_length); - size_t s = size + ALIGNED_LIST_ITEM_SIZE + M_DEBUG_NEW_TAILCHECK; - new_ptr_list_t* ptr = static_cast<new_ptr_list_t*>(malloc(s)); - if (ptr == nullptr) + STATIC_ASSERT(_DEBUG_NEW_TAILCHECK >= 0, Invalid_tail_check_length); + size_t s = size + ALIGNED_LIST_ITEM_SIZE + _DEBUG_NEW_TAILCHECK; + new_ptr_list_t* ptr = (new_ptr_list_t*)malloc(s); + if (ptr == _NULLPTR) { -#if M_DEBUG_NEW_STD_OPER_NEW - return nullptr; +#if _DEBUG_NEW_STD_OPER_NEW + return _NULLPTR; #else fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, - "Out of memory when allocating %u bytes\n", - size); + "Out of memory when allocating %lu bytes\n", + (unsigned long)size); fflush(new_output_fp); - M_DEBUG_NEW_ERROR_ACTION; + _DEBUG_NEW_ERROR_ACTION; #endif } - void* pointer = reinterpret_cast<char*>(ptr) + ALIGNED_LIST_ITEM_SIZE; -#if M_DEBUG_NEW_FILENAME_LEN == 0 + void* usr_ptr = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; +#if _DEBUG_NEW_FILENAME_LEN == 0 ptr->file = file; #else if (line) - { - strncpy(ptr->file, file, M_DEBUG_NEW_FILENAME_LEN - 1) - [M_DEBUG_NEW_FILENAME_LEN - 1] = '\0'; - } + strncpy(ptr->file, file, _DEBUG_NEW_FILENAME_LEN - 1) + [_DEBUG_NEW_FILENAME_LEN - 1] = '\0'; else + ptr->addr = (void*)file; +#endif + ptr->line = line; +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + ptr->stacktrace = _NULLPTR; + +#if _DEBUG_NEW_REMEMBER_STACK_TRACE == 2 + if (line == 0) +#endif { - ptr->addr = reinterpret_cast<void*>(const_cast<char*>(file)); + void* buffer [255]; + size_t buffer_length = sizeof(buffer) / sizeof(*buffer); + +#if NVWA_UNIX + int stacktrace_length = backtrace(buffer, int(buffer_length)); +#endif + +#if NVWA_WINDOWS + USHORT stacktrace_length = CaptureStackBackTrace( + 0, DWORD(buffer_length), buffer, _NULLPTR); +#endif + + size_t stacktrace_size = stacktrace_length * sizeof(void*); + ptr->stacktrace = (void**)malloc(stacktrace_size + sizeof(void*)); + + if (ptr->stacktrace != _NULLPTR) + { + memcpy(ptr->stacktrace, buffer, stacktrace_size); + ptr->stacktrace[stacktrace_length] = _NULLPTR; + } } #endif - ptr->line = line; ptr->is_array = is_array; ptr->size = size; - ptr->magic = MAGIC; + ptr->magic = DEBUG_NEW_MAGIC; { fast_mutex_autolock lock(new_ptr_lock); ptr->prev = new_ptr_list.prev; @@ -505,18 +651,17 @@ static void* alloc_mem(size_t size, const char* file, int line, bool is_array) new_ptr_list.prev->next = ptr; new_ptr_list.prev = ptr; } - ptr->dumped = 0; -#if M_DEBUG_NEW_TAILCHECK - memset((char*)pointer + size, M_DEBUG_NEW_TAILCHECK_CHAR, - M_DEBUG_NEW_TAILCHECK); +#if _DEBUG_NEW_TAILCHECK + memset((char*)usr_ptr + size, _DEBUG_NEW_TAILCHECK_CHAR, + _DEBUG_NEW_TAILCHECK); #endif if (new_verbose_flag) { fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, - "new%s: allocated %p (size %u, ", + "new%s: allocated %p (size %lu, ", is_array ? "[]" : "", - pointer, CAST_U32(size)); + usr_ptr, (unsigned long)size); if (line != 0) print_position(ptr->file, ptr->line); else @@ -524,49 +669,49 @@ static void* alloc_mem(size_t size, const char* file, int line, bool is_array) fprintf(new_output_fp, ")\n"); } total_mem_alloc += size; - return pointer; + return usr_ptr; } /** * Frees memory and adjusts pointers. * - * @param pointer pointer to delete + * @param usr_ptr pointer to the previously allocated memory * @param addr pointer to the caller * @param is_array flag indicating whether it is invoked by a - * <code>delete []</code> call + * <code>delete[]</code> call */ -static void free_pointer(void* pointer, void* addr, bool is_array) +static void free_pointer(void* usr_ptr, void* addr, bool is_array) { - if (pointer == nullptr) + if (usr_ptr == _NULLPTR) return; - new_ptr_list_t* ptr = reinterpret_cast<new_ptr_list_t*>( - static_cast<char*>(pointer) - ALIGNED_LIST_ITEM_SIZE); - if (ptr->magic != MAGIC) + new_ptr_list_t* ptr = + (new_ptr_list_t*)((char*)usr_ptr - ALIGNED_LIST_ITEM_SIZE); + if (ptr->magic != DEBUG_NEW_MAGIC) { { fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, "delete%s: invalid pointer %p (", - is_array ? "[]" : "", pointer); + is_array ? "[]" : "", usr_ptr); print_position(addr, 0); fprintf(new_output_fp, ")\n"); } check_mem_corruption(); fflush(new_output_fp); - M_DEBUG_NEW_ERROR_ACTION; + _DEBUG_NEW_ERROR_ACTION; } if (is_array != ptr->is_array) { const char* msg; if (is_array) - msg = "delete [] after new"; + msg = "delete[] after new"; else - msg = "delete after new []"; + msg = "delete after new[]"; fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, - "%s: pointer %p (size %u)\n\tat ", + "%s: pointer %p (size %lu)\n\tat ", msg, - reinterpret_cast<char*>(ptr) + ALIGNED_LIST_ITEM_SIZE, - CAST_U32(ptr->size)); + (char*)ptr + ALIGNED_LIST_ITEM_SIZE, + (unsigned long)ptr->size); print_position(addr, 0); fprintf(new_output_fp, "\n\toriginally allocated at "); if (ptr->line != 0) @@ -575,14 +720,14 @@ static void free_pointer(void* pointer, void* addr, bool is_array) print_position(ptr->addr, ptr->line); fprintf(new_output_fp, "\n"); fflush(new_output_fp); - M_DEBUG_NEW_ERROR_ACTION; + _DEBUG_NEW_ERROR_ACTION; } -#if M_DEBUG_NEW_TAILCHECK +#if _DEBUG_NEW_TAILCHECK if (!check_tail(ptr)) { check_mem_corruption(); fflush(new_output_fp); - M_DEBUG_NEW_ERROR_ACTION; + _DEBUG_NEW_ERROR_ACTION; } #endif { @@ -596,12 +741,14 @@ static void free_pointer(void* pointer, void* addr, bool is_array) { fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, - "delete%s: freed %p (size %u, %u bytes still allocated)\n", - is_array ? "[]" : "", - reinterpret_cast<char*>(ptr) + ALIGNED_LIST_ITEM_SIZE, - CAST_U32(ptr->size), - CAST_U32(total_mem_alloc)); + "delete%s: freed %p (size %lu, %lu bytes still allocated)\n", + is_array ? "[]" : "", + (char*)ptr + ALIGNED_LIST_ITEM_SIZE, + (unsigned long)ptr->size, (unsigned long)total_mem_alloc); } +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + free(ptr->stacktrace); +#endif free(ptr); return; } @@ -614,60 +761,69 @@ static void free_pointer(void* pointer, void* addr, bool is_array) int check_leaks() { int leak_cnt = 0; - int dumped_cnt = 0; - int new_cnt = 0; - unsigned long new_size = 0; + int whitelisted_leak_cnt = 0; fast_mutex_autolock lock_ptr(new_ptr_lock); fast_mutex_autolock lock_output(new_output_lock); new_ptr_list_t* ptr = new_ptr_list.next; - fprintf(new_output_fp, "---LEAKS LIST---\n"); while (ptr != &new_ptr_list) { - const char* const pointer = reinterpret_cast<const char *>(ptr) - + ALIGNED_LIST_ITEM_SIZE; - if (ptr->magic != MAGIC) + const char* const usr_ptr = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; + if (ptr->magic != DEBUG_NEW_MAGIC) { fprintf(new_output_fp, "warning: heap data corrupt near %p\n", - pointer); + usr_ptr); } -#if M_DEBUG_NEW_TAILCHECK +#if _DEBUG_NEW_TAILCHECK if (!check_tail(ptr)) { fprintf(new_output_fp, "warning: overwritten past end of object at %p\n", - pointer); + usr_ptr); } #endif -#ifndef DUMP_MEM_ADDRESSES - if (ptr->line != 0) -#endif + + if (is_leak_whitelisted(ptr)) + { + ++whitelisted_leak_cnt; + } + else { fprintf(new_output_fp, - "Leaked object at %p (size %u, dump %u, ", - pointer, CAST_U32(ptr->size), ptr->dumped); + "Leaked object at %p (size %lu, ", + usr_ptr, + (unsigned long)ptr->size); + if (ptr->line != 0) print_position(ptr->file, ptr->line); else print_position(ptr->addr, ptr->line); + fprintf(new_output_fp, ")\n"); - ++ new_cnt; - new_size += static_cast<unsigned long>(ptr->size); + +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + if (ptr->stacktrace != _NULLPTR) + print_stacktrace(ptr->stacktrace); +#endif } - if (ptr->dumped) - ++ dumped_cnt; - ++ ptr->dumped; ptr = ptr->next; - ++ leak_cnt; + ++leak_cnt; } if (new_verbose_flag || leak_cnt) { - fprintf(new_output_fp, "*** %d leaks found, new %d " - "(size %lu), dumped count %d\n", leak_cnt, new_cnt, - new_size, dumped_cnt); + if (whitelisted_leak_cnt > 0) + { + fprintf(new_output_fp, "*** %d leaks found (%d whitelisted)\n", + leak_cnt, whitelisted_leak_cnt); + } + else + { + fprintf(new_output_fp, "*** %d leaks found\n", leak_cnt); + } } + return leak_cnt; } @@ -684,35 +840,32 @@ int check_mem_corruption() fast_mutex_autolock lock_output(new_output_lock); fprintf(new_output_fp, "*** Checking for memory corruption: START\n"); for (new_ptr_list_t* ptr = new_ptr_list.next; - ptr != &new_ptr_list; - ptr = ptr->next) + ptr != &new_ptr_list; + ptr = ptr->next) { - const char* const pointer = reinterpret_cast<char*>(ptr) - + ALIGNED_LIST_ITEM_SIZE; - if (ptr->magic == MAGIC -#if M_DEBUG_NEW_TAILCHECK - && check_tail(ptr) + const char* const usr_ptr = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; + if (ptr->magic == DEBUG_NEW_MAGIC +#if _DEBUG_NEW_TAILCHECK + && check_tail(ptr) #endif - ) - { + ) continue; - } -#if M_DEBUG_NEW_TAILCHECK - if (ptr->magic != MAGIC) +#if _DEBUG_NEW_TAILCHECK + if (ptr->magic != DEBUG_NEW_MAGIC) { #endif fprintf(new_output_fp, - "Heap data corrupt near %p (size %u, ", - pointer, - CAST_U32(ptr->size)); -#if M_DEBUG_NEW_TAILCHECK + "Heap data corrupt near %p (size %lu, ", + usr_ptr, + (unsigned long)ptr->size); +#if _DEBUG_NEW_TAILCHECK } else { fprintf(new_output_fp, - "Overwritten past end of object at %p (size %u, ", - pointer, - ptr->size); + "Overwritten past end of object at %p (size %lu, ", + usr_ptr, + (unsigned long)ptr->size); } #endif if (ptr->line != 0) @@ -720,20 +873,50 @@ int check_mem_corruption() else print_position(ptr->addr, ptr->line); fprintf(new_output_fp, ")\n"); - ++ corrupt_cnt; + +#if _DEBUG_NEW_REMEMBER_STACK_TRACE + if (ptr->stacktrace != _NULLPTR) + print_stacktrace(ptr->stacktrace); +#endif + + ++corrupt_cnt; } fprintf(new_output_fp, "*** Checking for memory corruption: %d FOUND\n", corrupt_cnt); return corrupt_cnt; } -void __debug_new_recorder::_M_process(void* pointer) +/** + * Processes the allocated memory and inserts file/line informatin. + * It will only be done when it can ensure the memory is allocated by + * one of our operator new variants. + * + * @param usr_ptr pointer returned by a new-expression + */ +void debug_new_recorder::_M_process(void* usr_ptr) { - if (pointer == nullptr) + if (usr_ptr == _NULLPTR) return; - new_ptr_list_t* ptr = reinterpret_cast<new_ptr_list_t*>( - static_cast<char*>(pointer) - ALIGNED_LIST_ITEM_SIZE); - if (ptr->magic != MAGIC || ptr->line != 0) + + // In an expression `new NonPODType[size]', the pointer returned is + // not the pointer returned by operator new[], but offset by size_t + // to leave room for the size. It needs to be compensated here. + size_t offset = (char*)usr_ptr - (char*)_NULLPTR; + if (offset % PLATFORM_MEM_ALIGNMENT != 0) { + offset -= sizeof(size_t); + if (offset % PLATFORM_MEM_ALIGNMENT != 0) { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "warning: memory unaligned; skipping processing (%s:%d)\n", + _M_file, _M_line); + return; + } + usr_ptr = (char*)usr_ptr - sizeof(size_t); + } + + new_ptr_list_t* ptr = + (new_ptr_list_t*)((char*)usr_ptr - ALIGNED_LIST_ITEM_SIZE); + if (ptr->magic != DEBUG_NEW_MAGIC || ptr->line != 0) { fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, @@ -741,19 +924,78 @@ void __debug_new_recorder::_M_process(void* pointer) _M_file, _M_line); return; } -#if M_DEBUG_NEW_FILENAME_LEN == 0 + if (new_verbose_flag) { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "info: pointer %p allocated from %s:%d\n", + usr_ptr, _M_file, _M_line); + } +#if _DEBUG_NEW_FILENAME_LEN == 0 ptr->file = _M_file; #else - strncpy(ptr->file, _M_file, M_DEBUG_NEW_FILENAME_LEN - 1) - [M_DEBUG_NEW_FILENAME_LEN - 1] = '\0'; + strncpy(ptr->file, _M_file, _DEBUG_NEW_FILENAME_LEN - 1) + [_DEBUG_NEW_FILENAME_LEN - 1] = '\0'; #endif ptr->line = _M_line; +#if _DEBUG_NEW_REMEMBER_STACK_TRACE == 2 + free(ptr->stacktrace); + ptr->stacktrace = _NULLPTR; +#endif +} + +/** + * Count of source files that use debug_new. + */ +int debug_new_counter::_S_count = 0; + +/** + * Constructor to increment the count. + */ +debug_new_counter::debug_new_counter() +{ + ++_S_count; +} + +/** + * Destructor to decrement the count. When the count is zero, + * nvwa#check_leaks will be called. + */ +debug_new_counter::~debug_new_counter() +{ + if (--_S_count == 0 && new_autocheck_flag) + if (check_leaks()) + { + new_verbose_flag = true; +#if defined(__GNUC__) && __GNUC__ == 3 + if (!getenv("GLIBCPP_FORCE_NEW") && !getenv("GLIBCXX_FORCE_NEW")) + fprintf(new_output_fp, +"*** WARNING: GCC 3 is detected, please make sure the environment\n" +" variable GLIBCPP_FORCE_NEW (GCC 3.2 and 3.3) or GLIBCXX_FORCE_NEW\n" +" (GCC 3.4) is defined. Check the README file for details.\n"); +#endif + } } -void* operator new (size_t size, const char* file, int line) +NVWA_NAMESPACE_END + +#if NVWA_USE_NAMESPACE +using namespace nvwa; +#endif // NVWA_USE_NAMESPACE + +/** + * Allocates memory with file/line information. + * + * @param size size of the required memory block + * @param file null-terminated string of the file name + * @param line line number + * @return pointer to the memory allocated; or null if memory is + * insufficient (#_DEBUG_NEW_STD_OPER_NEW is 0) + * @throw bad_alloc memory is insufficient (#_DEBUG_NEW_STD_OPER_NEW is 1) + */ +void* operator new(size_t size, const char* file, int line) { void* ptr = alloc_mem(size, file, line, false); -#if M_DEBUG_NEW_STD_OPER_NEW +#if _DEBUG_NEW_STD_OPER_NEW if (ptr) return ptr; else @@ -763,10 +1005,20 @@ void* operator new (size_t size, const char* file, int line) #endif } -void* operator new [](size_t size, const char* file, int line) +/** + * Allocates array memory with file/line information. + * + * @param size size of the required memory block + * @param file null-terminated string of the file name + * @param line line number + * @return pointer to the memory allocated; or null if memory is + * insufficient (#_DEBUG_NEW_STD_OPER_NEW is 0) + * @throw bad_alloc memory is insufficient (#_DEBUG_NEW_STD_OPER_NEW is 1) + */ +void* operator new[](size_t size, const char* file, int line) { void* ptr = alloc_mem(size, file, line, true); -#if M_DEBUG_NEW_STD_OPER_NEW +#if _DEBUG_NEW_STD_OPER_NEW if (ptr) return ptr; else @@ -776,113 +1028,159 @@ void* operator new [](size_t size, const char* file, int line) #endif } -void* operator new (size_t size) // throw(std::bad_alloc) +/** + * Allocates memory without file/line information. + * + * @param size size of the required memory block + * @return pointer to the memory allocated; or null if memory is + * insufficient (#_DEBUG_NEW_STD_OPER_NEW is 0) + * @throw bad_alloc memory is insufficient (#_DEBUG_NEW_STD_OPER_NEW is 1) + */ +void* operator new(size_t size) throw(std::bad_alloc) +{ + return operator new(size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); +} + +/** + * Allocates array memory without file/line information. + * + * @param size size of the required memory block + * @return pointer to the memory allocated; or null if memory is + * insufficient (#_DEBUG_NEW_STD_OPER_NEW is 0) + * @throw bad_alloc memory is insufficient (#_DEBUG_NEW_STD_OPER_NEW is 1) + */ +void* operator new[](size_t size) throw(std::bad_alloc) { - return operator new (size, static_cast<char*>( - M_DEBUG_NEW_CALLER_ADDRESS), 0); + return operator new[](size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); } -void* operator new [](size_t size) // throw(std::bad_alloc) +/** + * Allocates memory with no-throw guarantee. + * + * @param size size of the required memory block + * @return pointer to the memory allocated; or null if memory is + * insufficient + */ +void* operator new(size_t size, const std::nothrow_t&) _NOEXCEPT { - return operator new [](size, static_cast<char*>( - M_DEBUG_NEW_CALLER_ADDRESS), 0); + return alloc_mem(size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0, false); } -#if !defined(__BORLANDC__) || __BORLANDC__ > 0x551 -void* operator new (size_t size, const std::nothrow_t&) throw() +/** + * Allocates array memory with no-throw guarantee. + * + * @param size size of the required memory block + * @return pointer to the memory allocated; or null if memory is + * insufficient + */ +void* operator new[](size_t size, const std::nothrow_t&) _NOEXCEPT { - return alloc_mem(size, static_cast<char*>( - M_DEBUG_NEW_CALLER_ADDRESS), 0, false); + return alloc_mem(size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0, true); } -void* operator new [](size_t size, const std::nothrow_t&) throw() +/** + * Deallocates memory. + * + * @param ptr pointer to the previously allocated memory + */ +void operator delete(void* ptr) _NOEXCEPT { - return alloc_mem(size, static_cast<char*>( - M_DEBUG_NEW_CALLER_ADDRESS), 0, true); + free_pointer(ptr, _DEBUG_NEW_CALLER_ADDRESS, false); } -#endif -void operator delete (void* pointer) throw() +/** + * Deallocates array memory. + * + * @param ptr pointer to the previously allocated memory + */ +void operator delete[](void* ptr) _NOEXCEPT +{ + free_pointer(ptr, _DEBUG_NEW_CALLER_ADDRESS, true); +} + +#if __cplusplus >= 201402L +// GCC under C++14 wants these definitions + +void operator delete(void* ptr, size_t) _NOEXCEPT { - free_pointer(pointer, M_DEBUG_NEW_CALLER_ADDRESS, false); + free_pointer(ptr, _DEBUG_NEW_CALLER_ADDRESS, false); } -void operator delete [](void* pointer) throw() +void operator delete[](void* ptr, size_t) _NOEXCEPT { - free_pointer(pointer, M_DEBUG_NEW_CALLER_ADDRESS, true); + free_pointer(ptr, _DEBUG_NEW_CALLER_ADDRESS, true); } +#endif -#if HAVE_PLACEMENT_DELETE -void operator delete (void* pointer, const char* file, int line) throw() +/** + * Placement deallocation function. For details, please check Section + * 5.3.4 of the C++ 1998 or 2011 Standard. + * + * @param ptr pointer to the previously allocated memory + * @param file null-terminated string of the file name + * @param line line number + * + * @see http://www.csci.csusb.edu/dick/c++std/cd2/expr.html#expr.new + * @see http://wyw.dcweb.cn/leakage.htm + */ +void operator delete(void* ptr, const char* file, int line) _NOEXCEPT { if (new_verbose_flag) { fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, "info: exception thrown on initializing object at %p (", - pointer); + ptr); print_position(file, line); fprintf(new_output_fp, ")\n"); } - operator delete (pointer); + operator delete(ptr); } -void operator delete [](void* pointer, const char* file, int line) throw() +/** + * Placement deallocation function. For details, please check Section + * 5.3.4 of the C++ 1998 or 2011 Standard. + * + * @param ptr pointer to the previously allocated memory + * @param file null-terminated string of the file name + * @param line line number + */ +void operator delete[](void* ptr, const char* file, int line) _NOEXCEPT { if (new_verbose_flag) { fast_mutex_autolock lock(new_output_lock); fprintf(new_output_fp, "info: exception thrown on initializing objects at %p (", - pointer); + ptr); print_position(file, line); fprintf(new_output_fp, ")\n"); } - operator delete [](pointer); -} - -void operator delete (void* pointer, const std::nothrow_t&) throw() -{ - operator delete (pointer, static_cast<char*>( - M_DEBUG_NEW_CALLER_ADDRESS), 0); + operator delete[](ptr); } -void operator delete [](void* pointer, const std::nothrow_t&) throw() -{ - operator delete [](pointer, static_cast<char*>( - M_DEBUG_NEW_CALLER_ADDRESS), 0); -} -#endif // HAVE_PLACEMENT_DELETE - -int __debug_new_counter::_S_count = 0; - /** - * Constructor to increment the count. + * Placement deallocation function. For details, please check Section + * 5.3.4 of the C++ 1998 or 2011 Standard. + * + * @param ptr pointer to the previously allocated memory */ -__debug_new_counter::__debug_new_counter() +void operator delete(void* ptr, const std::nothrow_t&) _NOEXCEPT { - ++_S_count; + operator delete(ptr, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); } /** - * Destructor to decrement the count. When the count is zero, - * #check_leaks will be called. + * Placement deallocation function. For details, please check Section + * 5.3.4 of the C++ 1998 or 2011 Standard. + * + * @param ptr pointer to the previously allocated memory */ -__debug_new_counter::~__debug_new_counter() +void operator delete[](void* ptr, const std::nothrow_t&) _NOEXCEPT { - if (--_S_count == 0 && new_autocheck_flag) - if (check_leaks()) - { - new_verbose_flag = true; -#if defined(__GNUC__) && __GNUC__ >= 3 - if (!getenv("GLIBCPP_FORCE_NEW") && !getenv("GLIBCXX_FORCE_NEW")) - fprintf(new_output_fp, -"*** WARNING: GCC 3 or later is detected, please make sure the\n" -" environment variable GLIBCPP_FORCE_NEW (GCC 3.2 and 3.3) or\n" -" GLIBCXX_FORCE_NEW (GCC 3.4 and later) is defined. Check the\n" -" README file for details.\n"); -#endif - } + operator delete[](ptr, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); } -#endif +// This is to make Doxygen happy +#undef _DEBUG_NEW_REMEMBER_STACK_TRACE +#define _DEBUG_NEW_REMEMBER_STACK_TRACE 0 |