From d2c42029b98c665725768b891aa877eb3267664f Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Tue, 24 May 2011 00:38:44 +0300 Subject: Add simple memory leak detector. Can be enabled by configure option -enable-memdebug=yes. Known issue: not all memory allocations hooked with it. --- src/debug/debug_new.cpp | 844 ++++++++++++++++++++++++++++++++++++++++++++++ src/debug/debug_new.h | 183 ++++++++++ src/debug/fast_mutex.h | 314 +++++++++++++++++ src/debug/static_assert.h | 53 +++ 4 files changed, 1394 insertions(+) create mode 100644 src/debug/debug_new.cpp create mode 100644 src/debug/debug_new.h create mode 100644 src/debug/fast_mutex.h create mode 100644 src/debug/static_assert.h (limited to 'src/debug') diff --git a/src/debug/debug_new.cpp b/src/debug/debug_new.cpp new file mode 100644 index 000000000..73b56bc7e --- /dev/null +++ b/src/debug/debug_new.cpp @@ -0,0 +1,844 @@ +// -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +// vim:tabstop=4:shiftwidth=4:expandtab: + +/* + * Copyright (C) 2004-2008 Wu Yongwei + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute + * it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must + * not claim that you wrote the original software. If you use this + * software in a product, an acknowledgement in the product + * documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must + * not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source + * distribution. + * + * This file is part of Stones of Nvwa: + * http://sourceforge.net/projects/nvwa + * + * original version changed for ManaPlus + * + * Copyright (C) 2011 ManaPlus developers + */ + +/** + * @file debug_new.cpp + * + * Implementation of debug versions of new and delete to check leakage. + * + * @version 4.14, 2008/10/20 + * @author Wu Yongwei + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef __unix__ +#include +#endif +#ifdef _WIN32 +#include +#endif +#include "debug/fast_mutex.h" +#include "debug/static_assert.h" + +#if !_FAST_MUTEX_CHECK_INITIALIZATION && !defined(_NOTHREADS) +#error "_FAST_MUTEX_CHECK_INITIALIZATION not set: check_leaks may not work" +#endif + +/** + * @def _DEBUG_NEW_ALIGNMENT + * + * The alignment requirement of allocated memory blocks. It must be a + * power of two. + */ +#ifndef _DEBUG_NEW_ALIGNMENT +#define _DEBUG_NEW_ALIGNMENT 16 +#endif + +/** + * @def _DEBUG_NEW_CALLER_ADDRESS + * + * The expression to return the caller address. #print_position will + * later on use this address to print the position information of memory + * operation points. + */ +#ifndef _DEBUG_NEW_CALLER_ADDRESS +#ifdef __GNUC__ +#define _DEBUG_NEW_CALLER_ADDRESS __builtin_return_address(0) +#else +#define _DEBUG_NEW_CALLER_ADDRESS NULL +#endif +#endif + +/** + * @def _DEBUG_NEW_ERROR_ACTION + * + * The action to take when an error occurs. The default behaviour is to + * 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 _DEBUG_NEW_ERROR_ACTION +#ifndef _DEBUG_NEW_ERROR_CRASH +#define _DEBUG_NEW_ERROR_ACTION abort() +#else +#define _DEBUG_NEW_ERROR_ACTION do { *((char*)0) = 0; abort(); } while (0) +#endif +#endif + +/** + * @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. + */ +#ifndef _DEBUG_NEW_FILENAME_LEN +#define _DEBUG_NEW_FILENAME_LEN 44 +#endif + +/** + * @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 + * (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 -D_DEBUG_NEW_PROGNAME=\"a.out\" + * in \e bash, or -D_DEBUG_NEW_PROGNAME=\"a.exe\" in the + * Windows command prompt. + */ +#ifndef _DEBUG_NEW_PROGNAME +#define _DEBUG_NEW_PROGNAME NULL +#endif + +/** + * @def _DEBUG_NEW_STD_OPER_NEW + * + * Macro to indicate whether the standard-conformant behaviour of + * operator new is wanted. It is on by default now, but + * the user may set it to \c 0 to revert to the old behaviour. + */ +#ifndef _DEBUG_NEW_STD_OPER_NEW +#define _DEBUG_NEW_STD_OPER_NEW 1 +#endif + +/** + * @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 _DEBUG_NEW_TAILCHECK +#define _DEBUG_NEW_TAILCHECK 0 +#endif + +/** + * @def _DEBUG_NEW_TAILCHECK_CHAR + * + * Value of the padding bytes at the end of a memory block. + */ +#ifndef _DEBUG_NEW_TAILCHECK_CHAR +#define _DEBUG_NEW_TAILCHECK_CHAR 0xCC +#endif + +/** + * @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 _DEBUG_NEW_USE_ADDR2LINE +#ifdef __GNUC__ +#define _DEBUG_NEW_USE_ADDR2LINE 1 +#else +#define _DEBUG_NEW_USE_ADDR2LINE 0 +#endif +#endif + +#ifdef _MSC_VER +#pragma warning(disable: 4073) // #pragma init_seg(lib) used +#pragma warning(disable: 4290) // C++ exception specification ignored +#pragma init_seg(lib) +#endif + +#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_new.h" + +/** + * Gets the aligned value of memory block size. + */ +#define align(s) \ + (((s) + _DEBUG_NEW_ALIGNMENT - 1) & ~(_DEBUG_NEW_ALIGNMENT - 1)) + +/** + * 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; + union + { +#if _DEBUG_NEW_FILENAME_LEN == 0 + const char* file; +#else + char file[_DEBUG_NEW_FILENAME_LEN]; +#endif + void* addr; + }; + unsigned line :31; + unsigned is_array :1; + unsigned magic; +}; + +/** + * Magic number for error detection. + */ +const unsigned MAGIC = 0x4442474E; + +/** + * The extra memory allocated by operator new. + */ +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 = { + &new_ptr_list, + &new_ptr_list, + 0, + { +#if _DEBUG_NEW_FILENAME_LEN == 0 + NULL +#else + "" +#endif + }, + 0, + 0, + MAGIC +}; + +/** + * The mutex guard to protect simultaneous access to the pointer list. + */ +static fast_mutex new_ptr_lock; + +/** + * The mutex guard to protect simultaneous output to #new_output_fp. + */ +static fast_mutex new_output_lock; + +/** + * Total memory allocated in bytes. + */ +static size_t total_mem_alloc = 0; + +/** + * Flag to control whether #check_leaks will be automatically called on + * program exit. + */ +bool new_autocheck_flag = true; + +/** + * Flag to control whether verbose messages are output. + */ +bool new_verbose_flag = false; + +/** + * Pointer to the output stream. The default output is \e stderr, and + * one may change it to a user stream if needed (say, #new_verbose_flag + * is \c true and there are a lot of (de)allocations). + */ +FILE* new_output_fp = stderr; + +/** + * Pointer to the program name. Its initial value is the macro + * #_DEBUG_NEW_PROGNAME. You should try to assign the program path to + * it early in your application. Assigning argv[0] 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: + * `new_progname = getenv("_");'. + */ +const char* new_progname = _DEBUG_NEW_PROGNAME; + +#if _DEBUG_NEW_USE_ADDR2LINE +/** + * Tries printing the position information from an instruction address. + * This is the version that uses \e addr2line. + * + * @param addr the instruction address to convert and print + * @return \c true if the address is converted successfully (and + * the result is printed); \c false if no useful + * information is got (and nothing is printed) + */ +static bool print_position_from_addr(const void* addr) +{ + static const void* last_addr = NULL; + static char last_info[256] = ""; + if (addr == last_addr) + { + if (last_info[0] == '\0') + return false; + fprintf(new_output_fp, "%s", last_info); + return true; + } + if (new_progname) + { + const char addr2line_cmd[] = "addr2line -e "; +#if defined(__CYGWIN__) || defined(_WIN32) + const int exeext_len = 4; +#else + const int exeext_len = 0; +#endif +#if !defined(__CYGWIN__) && defined(__unix__) + const char ignore_err[] = " 2>/dev/null"; +#elif defined(__CYGWIN__) || \ + (defined(_WIN32) && defined(WINVER) && WINVER >= 0x0500) + const char ignore_err[] = " 2>nul"; +#else + const char ignore_err[] = ""; +#endif + 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)) + { + strcpy(cmd + len, ".exe"); + len += 4; + } +#endif + sprintf(cmd + len, " %p%s", addr, ignore_err); + FILE* fp = popen(cmd, "r"); + if (fp) + { + char buffer[sizeof last_info] = ""; + len = 0; + if (fgets(buffer, sizeof buffer, fp)) + { + len = strlen(buffer); + if (buffer[len - 1] == '\n') + buffer[--len] = '\0'; + } + int res = pclose(fp); + // Display the file/line information only if the command + // is executed successfully and the output points to a + // valid position, but the result will be cached if only + // the command is executed successfully. + if (res == 0 && len > 0) + { + last_addr = addr; + if (buffer[len - 1] == '0' && buffer[len - 2] == ':') + last_info[0] = '\0'; + else + { + fprintf(new_output_fp, "%s", buffer); + strcpy(last_info, buffer); + return true; + } + } + } + } + return false; +} +#else +/** + * Tries printing the position information from an instruction address. + * This is the stub version that does nothing at all. + * + * @return \c false always + */ +static bool print_position_from_addr(const void*) +{ + return false; +} +#endif // _DEBUG_NEW_USE_ADDR2LINE + +/** + * Prints the position information of a memory operation point. When \c + * _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. + * + * @param ptr source file name if \e line is non-zero; caller address + * otherwise + * @param line source line number if non-zero; indication that \e ptr + * is the caller address otherwise + */ +static void print_position(const void* ptr, int line) +{ + if (line != 0) // Is file/line information present? + { + fprintf(new_output_fp, "%s:%d", (const char*)ptr, line); + } + else if (ptr != NULL) // Is caller address present? + { + if (!print_position_from_addr(ptr)) // Fail to get source position? + fprintf(new_output_fp, "%p", ptr); + } + else // No information is present + { + fprintf(new_output_fp, ""); + } +} + +#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 + */ +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 < _DEBUG_NEW_TAILCHECK; ++i) + if (pointer[i] != _DEBUG_NEW_TAILCHECK_CHAR) + return false; + return true; +} +#endif + +/** + * Allocates memory and initializes control data. + * + * @param size size of the required memory block + * @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 + */ +static void* alloc_mem(size_t size, const char* file, int line, bool is_array) +{ + assert(line >= 0); + STATIC_ASSERT((_DEBUG_NEW_ALIGNMENT & (_DEBUG_NEW_ALIGNMENT - 1)) == 0, + Alignment_must_be_power_of_two); + 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 == NULL) + { +#if _DEBUG_NEW_STD_OPER_NEW + return NULL; +#else + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "Out of memory when allocating %u bytes\n", + size); + fflush(new_output_fp); + _DEBUG_NEW_ERROR_ACTION; +#endif + } + void* pointer = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; +#if _DEBUG_NEW_FILENAME_LEN == 0 + ptr->file = file; +#else + if (line) + strncpy(ptr->file, file, _DEBUG_NEW_FILENAME_LEN - 1) + [_DEBUG_NEW_FILENAME_LEN - 1] = '\0'; + else + ptr->addr = (void*)file; +#endif + ptr->line = line; + ptr->is_array = is_array; + ptr->size = size; + ptr->magic = MAGIC; + { + fast_mutex_autolock lock(new_ptr_lock); + ptr->prev = new_ptr_list.prev; + ptr->next = &new_ptr_list; + new_ptr_list.prev->next = ptr; + new_ptr_list.prev = ptr; + } +#if _DEBUG_NEW_TAILCHECK + memset((char*)pointer + 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, ", + is_array ? "[]" : "", + pointer, (unsigned)size); + if (line != 0) + print_position(ptr->file, ptr->line); + else + print_position(ptr->addr, ptr->line); + fprintf(new_output_fp, ")\n"); + } + total_mem_alloc += size; + return pointer; +} + +/** + * Frees memory and adjusts pointers. + * + * @param pointer pointer to delete + * @param addr pointer to the caller + * @param is_array flag indicating whether it is invoked by a + * delete[] call + */ +static void free_pointer(void* pointer, void* addr, bool is_array) +{ + if (pointer == NULL) + return; + new_ptr_list_t* ptr = + (new_ptr_list_t*)((char*)pointer - ALIGNED_LIST_ITEM_SIZE); + if (ptr->magic != MAGIC) + { + { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, "delete%s: invalid pointer %p (", + is_array ? "[]" : "", pointer); + print_position(addr, 0); + fprintf(new_output_fp, ")\n"); + } + check_mem_corruption(); + fflush(new_output_fp); + _DEBUG_NEW_ERROR_ACTION; + } + if (is_array != ptr->is_array) + { + const char* msg; + if (is_array) + msg = "delete[] after new"; + else + msg = "delete after new[]"; + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "%s: pointer %p (size %u)\n\tat ", + msg, + (char*)ptr + ALIGNED_LIST_ITEM_SIZE, + (unsigned)ptr->size); + print_position(addr, 0); + fprintf(new_output_fp, "\n\toriginally allocated at "); + if (ptr->line != 0) + print_position(ptr->file, ptr->line); + else + print_position(ptr->addr, ptr->line); + fprintf(new_output_fp, "\n"); + fflush(new_output_fp); + _DEBUG_NEW_ERROR_ACTION; + } +#if _DEBUG_NEW_TAILCHECK + if (!check_tail(ptr)) + { + check_mem_corruption(); + fflush(new_output_fp); + _DEBUG_NEW_ERROR_ACTION; + } +#endif + { + fast_mutex_autolock lock(new_ptr_lock); + total_mem_alloc -= ptr->size; + ptr->magic = 0; + ptr->prev->next = ptr->next; + ptr->next->prev = ptr->prev; + } + if (new_verbose_flag) + { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "delete%s: freed %p (size %u, %u bytes still allocated)\n", + is_array ? "[]" : "", + (char*)ptr + ALIGNED_LIST_ITEM_SIZE, + (unsigned)ptr->size, (unsigned)total_mem_alloc); + } + free(ptr); + return; +} + +/** + * Checks for memory leaks. + * + * @return zero if no leakage is found; the number of leaks otherwise + */ +int check_leaks() +{ + int 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; + while (ptr != &new_ptr_list) + { + const char* const pointer = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; + if (ptr->magic != MAGIC) + { + fprintf(new_output_fp, + "warning: heap data corrupt near %p\n", + pointer); + } +#if _DEBUG_NEW_TAILCHECK + if (!check_tail(ptr)) + { + fprintf(new_output_fp, + "warning: overwritten past end of object at %p\n", + pointer); + } +#endif + fprintf(new_output_fp, + "Leaked object at %p (size %u, ", + pointer, + (unsigned)ptr->size); + if (ptr->line != 0) + print_position(ptr->file, ptr->line); + else + print_position(ptr->addr, ptr->line); + fprintf(new_output_fp, ")\n"); + ptr = ptr->next; + ++leak_cnt; + } + if (new_verbose_flag || leak_cnt) + fprintf(new_output_fp, "*** %d leaks found\n", leak_cnt); + return leak_cnt; +} + +/** + * Checks for heap corruption. + * + * @return zero if no problem is found; the number of found memory + * corruptions otherwise + */ +int check_mem_corruption() +{ + int corrupt_cnt = 0; + fast_mutex_autolock lock_ptr(new_ptr_lock); + 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) + { + const char* const pointer = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; + if (ptr->magic == MAGIC +#if _DEBUG_NEW_TAILCHECK + && check_tail(ptr) +#endif + ) + continue; +#if _DEBUG_NEW_TAILCHECK + if (ptr->magic != MAGIC) + { +#endif + fprintf(new_output_fp, + "Heap data corrupt near %p (size %u, ", + pointer, + (unsigned)ptr->size); +#if _DEBUG_NEW_TAILCHECK + } + else + { + fprintf(new_output_fp, + "Overwritten past end of object at %p (size %u, ", + pointer, + ptr->size); + } +#endif + if (ptr->line != 0) + print_position(ptr->file, ptr->line); + else + print_position(ptr->addr, ptr->line); + fprintf(new_output_fp, ")\n"); + ++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) +{ + if (pointer == NULL) + return; + new_ptr_list_t* ptr = + (new_ptr_list_t*)((char*)pointer - ALIGNED_LIST_ITEM_SIZE); + if (ptr->magic != MAGIC || ptr->line != 0) + { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "warning: debug_new used with placement new (%s:%d)\n", + _M_file, _M_line); + return; + } +#if _DEBUG_NEW_FILENAME_LEN == 0 + ptr->file = _M_file; +#else + strncpy(ptr->file, _M_file, _DEBUG_NEW_FILENAME_LEN - 1) + [_DEBUG_NEW_FILENAME_LEN - 1] = '\0'; +#endif + ptr->line = _M_line; +} + +void* operator new(size_t size, const char* file, int line) +{ + void* ptr = alloc_mem(size, file, line, false); +#if _DEBUG_NEW_STD_OPER_NEW + if (ptr) + return ptr; + else + throw std::bad_alloc(); +#else + return ptr; +#endif +} + +void* operator new[](size_t size, const char* file, int line) +{ + void* ptr = alloc_mem(size, file, line, true); +#if _DEBUG_NEW_STD_OPER_NEW + if (ptr) + return ptr; + else + throw std::bad_alloc(); +#else + return ptr; +#endif +} + +void* operator new(size_t size) throw(std::bad_alloc) +{ + return operator new(size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); +} + +void* operator new[](size_t size) throw(std::bad_alloc) +{ + return operator new[](size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); +} + +#if !defined(__BORLANDC__) || __BORLANDC__ > 0x551 +void* operator new(size_t size, const std::nothrow_t&) throw() +{ + return alloc_mem(size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0, false); +} + +void* operator new[](size_t size, const std::nothrow_t&) throw() +{ + return alloc_mem(size, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0, true); +} +#endif + +void operator delete(void* pointer) throw() +{ + free_pointer(pointer, _DEBUG_NEW_CALLER_ADDRESS, false); +} + +void operator delete[](void* pointer) throw() +{ + free_pointer(pointer, _DEBUG_NEW_CALLER_ADDRESS, true); +} + +#if HAVE_PLACEMENT_DELETE +void operator delete(void* pointer, const char* file, int line) throw() +{ + if (new_verbose_flag) + { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "info: exception thrown on initializing object at %p (", + pointer); + print_position(file, line); + fprintf(new_output_fp, ")\n"); + } + operator delete(pointer); +} + +void operator delete[](void* pointer, const char* file, int line) throw() +{ + if (new_verbose_flag) + { + fast_mutex_autolock lock(new_output_lock); + fprintf(new_output_fp, + "info: exception thrown on initializing objects at %p (", + pointer); + 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, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); +} + +void operator delete[](void* pointer, const std::nothrow_t&) throw() +{ + operator delete[](pointer, (char*)_DEBUG_NEW_CALLER_ADDRESS, 0); +} +#endif // HAVE_PLACEMENT_DELETE + +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, + * #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 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 + } +} diff --git a/src/debug/debug_new.h b/src/debug/debug_new.h new file mode 100644 index 000000000..5bcd06eb0 --- /dev/null +++ b/src/debug/debug_new.h @@ -0,0 +1,183 @@ +// -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +// vim:tabstop=4:shiftwidth=4:expandtab: + +/* + * Copyright (C) 2004-2008 Wu Yongwei + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute + * it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must + * not claim that you wrote the original software. If you use this + * software in a product, an acknowledgement in the product + * documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must + * not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source + * distribution. + * + * This file is part of Stones of Nvwa: + * http://sourceforge.net/projects/nvwa + * + * original version changed for ManaPlus + * + * Copyright (C) 2011 ManaPlus developers + */ + +/** + * @file debug_new.h + * + * Header file for checking leaks caused by unmatched new/delete. + * + * @version 4.4, 2007/12/31 + * @author Wu Yongwei + * + */ + +#ifndef _DEBUG_NEW_H +#define _DEBUG_NEW_H + +#include +#include + +/** + * @def HAVE_PLACEMENT_DELETE + * + * Macro to indicate whether placement delete operators are supported on + * a certain compiler. Some compilers, like Borland C++ Compiler 5.5.1 + * and Digital Mars Compiler 8.42, do not support them, and the user + * must define this macro to \c 0 to make the program compile. Also + * note that in that case memory leakage will occur if an exception is + * thrown in the initialization (constructor) of a dynamically created + * object. + */ +#ifndef HAVE_PLACEMENT_DELETE +#define HAVE_PLACEMENT_DELETE 1 +#endif + +/** + * @def _DEBUG_NEW_REDEFINE_NEW + * + * Macro to indicate whether redefinition of \c new is wanted. If one + * wants to define one's own operator new, to call + * operator new directly, or to call placement \c new, it + * should be defined to \c 0 to alter the default behaviour. Unless, of + * course, one is willing to take the trouble to write something like: + * @code + * # ifdef new + * # define _NEW_REDEFINED + * # undef new + * # endif + * + * // Code that uses new is here + * + * # ifdef _NEW_REDEFINED + * # ifdef DEBUG_NEW + * # define new DEBUG_NEW + * # endif + * # undef _NEW_REDEFINED + * # endif + * @endcode + */ +#ifndef _DEBUG_NEW_REDEFINE_NEW +#define _DEBUG_NEW_REDEFINE_NEW 1 +#endif + +/* Prototypes */ +int check_leaks(); +int check_mem_corruption(); +void* operator new(size_t size, const char* file, int line); +void* operator new[](size_t size, const char* file, int line); +#if HAVE_PLACEMENT_DELETE +void operator delete(void* pointer, const char* file, int line) throw(); +void operator delete[](void* pointer, const char* file, int line) throw(); +#endif +#if defined(_MSC_VER) && _MSC_VER < 1300 +// MSVC 6 requires the following declarations; or the non-placement +// new[]/delete[] will not compile. +void* operator new[](size_t) throw(std::bad_alloc); +void operator delete[](void*) throw(); +#endif + +/* Control variables */ +extern bool new_autocheck_flag; // default to true: call check_leaks() on exit +extern bool new_verbose_flag; // default to false: no verbose information +extern FILE* new_output_fp; // default to stderr: output to console +extern const char* new_progname;// default to NULL; should be assigned argv[0] + +/** + * @def DEBUG_NEW + * + * Macro to catch file/line information on allocation. If + * #_DEBUG_NEW_REDEFINE_NEW is \c 0, one can use this macro directly; + * otherwise \c new will be defined to it, and one must use \c new + * instead. + */ +#define DEBUG_NEW __debug_new_recorder(__FILE__, __LINE__) ->* new + +# if _DEBUG_NEW_REDEFINE_NEW +# define new DEBUG_NEW +# endif +# ifdef _DEBUG_NEW_EMULATE_MALLOC +# include +# ifdef new +# define malloc(s) ((void*)(new char[s])) +# else +# define malloc(s) ((void*)(DEBUG_NEW char[s])) +# endif +# define free(p) delete[] (char*)(p) +# define default_free free +# endif + +/** + * Recorder class to remember the call context. + * + * The idea comes from Greg Herlihy's post in comp.lang.c++.moderated. + */ +class __debug_new_recorder +{ + const char* _M_file; + const int _M_line; + void _M_process(void* pointer); +public: + /** + * Constructor to remember the call context. The information will + * be used in __debug_new_recorder::operator->*. + */ + __debug_new_recorder(const char* file, int line) + : _M_file(file), _M_line(line) {} + /** + * Operator to write the context information to memory. + * operator->* is chosen because it has the right + * precedence, it is rarely used, and it looks good: so people can + * tell the special usage more quickly. + */ + template _Tp* operator->*(_Tp* pointer) + { _M_process(pointer); return pointer; } +private: + __debug_new_recorder(const __debug_new_recorder&); + __debug_new_recorder& operator=(const __debug_new_recorder&); +}; + +/** + * Counter class for on-exit leakage check. + * + * This technique is learnt from The C++ Programming Language by + * Bjarne Stroustup. + */ +class __debug_new_counter +{ + static int _S_count; +public: + __debug_new_counter(); + ~__debug_new_counter(); +}; +/** Counting object for each file including debug_new.h. */ +static __debug_new_counter __debug_new_count; + +#endif // _DEBUG_NEW_H diff --git a/src/debug/fast_mutex.h b/src/debug/fast_mutex.h new file mode 100644 index 000000000..a6adb2db3 --- /dev/null +++ b/src/debug/fast_mutex.h @@ -0,0 +1,314 @@ +// -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +// vim:tabstop=4:shiftwidth=4:expandtab: + +/* + * Copyright (C) 2004-2008 Wu Yongwei + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute + * it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must + * not claim that you wrote the original software. If you use this + * software in a product, an acknowledgement in the product + * documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must + * not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source + * distribution. + * + * This file is part of Stones of Nvwa: + * http://sourceforge.net/projects/nvwa + * + * original version changed for ManaPlus + * + * Copyright (C) 2011 ManaPlus developers + */ + +/** + * @file fast_mutex.h + * + * A fast mutex implementation for POSIX and Win32. + * + * @version 1.18, 2005/05/06 + * @author Wu Yongwei + * + */ + +#ifndef _FAST_MUTEX_H +#define _FAST_MUTEX_H + +# if !defined(_NOTHREADS) +# if !defined(_WIN32THREADS) && \ + (defined(_WIN32) && defined(_MT)) +// Automatically use _WIN32THREADS when specifying -MT/-MD in MSVC, +// or -mthreads in MinGW GCC. +# define _WIN32THREADS +# elif !defined(_PTHREADS) && \ + defined(_REENTRANT) +// Automatically use _PTHREADS when specifying -pthread in GCC. +// N.B. I do not detect on _PTHREAD_H since libstdc++-v3 under +// Linux will silently include anyway. +# define _PTHREADS +# endif +# endif + +# if !defined(_PTHREADS) && !defined(_WIN32THREADS) && !defined(_NOTHREADS) +# define _NOTHREADS +# endif + +# if defined(_NOTHREADS) +# if defined(_PTHREADS) || defined(_WIN32THREADS) +# undef _NOTHREADS +# error "Cannot define multi-threaded mode with -D_NOTHREADS" +# if defined(__MINGW32__) && defined(_WIN32THREADS) && !defined(_MT) +# error "Be sure to specify -mthreads with -D_WIN32THREADS" +# endif +# endif +# endif + +# ifndef _FAST_MUTEX_CHECK_INITIALIZATION +/** + * Macro to control whether to check for initialization status for each + * lock/unlock operation. Defining it to a non-zero value will enable + * the check, so that the construction/destruction of a static object + * using a static fast_mutex not yet constructed or already destroyed + * will work (with lock/unlock operations ignored). Defining it to zero + * will disable to check. + */ +# define _FAST_MUTEX_CHECK_INITIALIZATION 1 +# endif + +# if defined(_PTHREADS) && defined(_WIN32THREADS) +// Some C++ libraries have _PTHREADS defined even on Win32 platforms. +// Thus this hack. +# undef _PTHREADS +# endif + +# ifdef _DEBUG +# include +# include +/** Macro for fast_mutex assertions. Real version (for debug mode). */ +# define _FAST_MUTEX_ASSERT(_Expr, _Msg) \ + if (!(_Expr)) { \ + fprintf(stderr, "fast_mutex::%s\n", _Msg); \ + abort(); \ + } +# else +/** Macro for fast_mutex assertions. Fake version (for release mode). */ +# define _FAST_MUTEX_ASSERT(_Expr, _Msg) \ + ((void)0) +# endif + +# ifdef _PTHREADS +# include +/** + * Macro alias to `volatile' semantics. Here it is truly volatile since + * it is in a multi-threaded (POSIX threads) environment. + */ +# define __VOLATILE volatile + /** + * Class for non-reentrant fast mutexes. This is the implementation + * for POSIX threads. + */ + class fast_mutex + { + pthread_mutex_t _M_mtx_impl; +# if _FAST_MUTEX_CHECK_INITIALIZATION + bool _M_initialized; +# endif +# ifdef _DEBUG + bool _M_locked; +# endif + public: + fast_mutex() +# ifdef _DEBUG + : _M_locked(false) +# endif + { + ::pthread_mutex_init(&_M_mtx_impl, NULL); +# if _FAST_MUTEX_CHECK_INITIALIZATION + _M_initialized = true; +# endif + } + ~fast_mutex() + { + _FAST_MUTEX_ASSERT(!_M_locked, "~fast_mutex(): still locked"); +# if _FAST_MUTEX_CHECK_INITIALIZATION + _M_initialized = false; +# endif + ::pthread_mutex_destroy(&_M_mtx_impl); + } + void lock() + { +# if _FAST_MUTEX_CHECK_INITIALIZATION + if (!_M_initialized) + return; +# endif + ::pthread_mutex_lock(&_M_mtx_impl); +# ifdef _DEBUG + // The following assertion should _always_ be true for a + // real `fast' pthread_mutex. However, this assertion can + // help sometimes, when people forget to use `-lpthread' and + // glibc provides an empty implementation. Having this + // assertion is also more consistent. + _FAST_MUTEX_ASSERT(!_M_locked, "lock(): already locked"); + _M_locked = true; +# endif + } + void unlock() + { +# if _FAST_MUTEX_CHECK_INITIALIZATION + if (!_M_initialized) + return; +# endif +# ifdef _DEBUG + _FAST_MUTEX_ASSERT(_M_locked, "unlock(): not locked"); + _M_locked = false; +# endif + ::pthread_mutex_unlock(&_M_mtx_impl); + } + private: + fast_mutex(const fast_mutex&); + fast_mutex& operator=(const fast_mutex&); + }; +# endif // _PTHREADS + +# ifdef _WIN32THREADS +# include +/** + * Macro alias to `volatile' semantics. Here it is truly volatile since + * it is in a multi-threaded (Win32 threads) environment. + */ +# define __VOLATILE volatile + /** + * Class for non-reentrant fast mutexes. This is the implementation + * for Win32 threads. + */ + class fast_mutex + { + CRITICAL_SECTION _M_mtx_impl; +# if _FAST_MUTEX_CHECK_INITIALIZATION + bool _M_initialized; +# endif +# ifdef _DEBUG + bool _M_locked; +# endif + public: + fast_mutex() +# ifdef _DEBUG + : _M_locked(false) +# endif + { + ::InitializeCriticalSection(&_M_mtx_impl); +# if _FAST_MUTEX_CHECK_INITIALIZATION + _M_initialized = true; +# endif + } + ~fast_mutex() + { + _FAST_MUTEX_ASSERT(!_M_locked, "~fast_mutex(): still locked"); +# if _FAST_MUTEX_CHECK_INITIALIZATION + _M_initialized = false; +# endif + ::DeleteCriticalSection(&_M_mtx_impl); + } + void lock() + { +# if _FAST_MUTEX_CHECK_INITIALIZATION + if (!_M_initialized) + return; +# endif + ::EnterCriticalSection(&_M_mtx_impl); +# ifdef _DEBUG + _FAST_MUTEX_ASSERT(!_M_locked, "lock(): already locked"); + _M_locked = true; +# endif + } + void unlock() + { +# if _FAST_MUTEX_CHECK_INITIALIZATION + if (!_M_initialized) + return; +# endif +# ifdef _DEBUG + _FAST_MUTEX_ASSERT(_M_locked, "unlock(): not locked"); + _M_locked = false; +# endif + ::LeaveCriticalSection(&_M_mtx_impl); + } + private: + fast_mutex(const fast_mutex&); + fast_mutex& operator=(const fast_mutex&); + }; +# endif // _WIN32THREADS + +# ifdef _NOTHREADS +/** + * Macro alias to `volatile' semantics. Here it is not truly volatile + * since it is in a single-threaded environment. + */ +# define __VOLATILE + /** + * Class for non-reentrant fast mutexes. This is the null + * implementation for single-threaded environments. + */ + class fast_mutex + { +# ifdef _DEBUG + bool _M_locked; +# endif + public: + fast_mutex() +# ifdef _DEBUG + : _M_locked(false) +# endif + { + } + ~fast_mutex() + { + _FAST_MUTEX_ASSERT(!_M_locked, "~fast_mutex(): still locked"); + } + void lock() + { +# ifdef _DEBUG + _FAST_MUTEX_ASSERT(!_M_locked, "lock(): already locked"); + _M_locked = true; +# endif + } + void unlock() + { +# ifdef _DEBUG + _FAST_MUTEX_ASSERT(_M_locked, "unlock(): not locked"); + _M_locked = false; +# endif + } + private: + fast_mutex(const fast_mutex&); + fast_mutex& operator=(const fast_mutex&); + }; +# endif // _NOTHREADS + +/** An acquistion-on-initialization lock class based on fast_mutex. */ +class fast_mutex_autolock +{ + fast_mutex& _M_mtx; +public: + explicit fast_mutex_autolock(fast_mutex& __mtx) : _M_mtx(__mtx) + { + _M_mtx.lock(); + } + ~fast_mutex_autolock() + { + _M_mtx.unlock(); + } +private: + fast_mutex_autolock(const fast_mutex_autolock&); + fast_mutex_autolock& operator=(const fast_mutex_autolock&); +}; + +#endif // _FAST_MUTEX_H diff --git a/src/debug/static_assert.h b/src/debug/static_assert.h new file mode 100644 index 000000000..17be7a5f3 --- /dev/null +++ b/src/debug/static_assert.h @@ -0,0 +1,53 @@ +// -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +// vim:tabstop=4:shiftwidth=4:expandtab: + +/* + * Copyright (C) 2004-2008 Wu Yongwei + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute + * it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must + * not claim that you wrote the original software. If you use this + * software in a product, an acknowledgement in the product + * documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must + * not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source + * distribution. + * + * This file is part of Stones of Nvwa: + * http://sourceforge.net/projects/nvwa + * + * original version changed for ManaPlus + * + * Copyright (C) 2011 ManaPlus developers + */ + +/** + * @file static_assert.h + * + * Template class to check validity duing compile time (adapted from Loki). + * + * @version 1.2, 2005/11/22 + * @author Wu Yongwei + * + */ + +#ifndef STATIC_ASSERT + +template struct __nvwa_compile_time_error; +template <> struct __nvwa_compile_time_error {}; + +#define STATIC_ASSERT(_Expr, _Msg) \ + { \ + __nvwa_compile_time_error<((_Expr) != 0)> ERROR_##_Msg; \ + (void)ERROR_##_Msg; \ + } + +#endif // STATIC_ASSERT -- cgit v1.2.3-60-g2f50