summaryrefslogtreecommitdiff
path: root/src/debug
diff options
context:
space:
mode:
authorAndrei Karas <akaras@inbox.ru>2011-05-24 00:38:44 +0300
committerAndrei Karas <akaras@inbox.ru>2011-05-24 00:38:44 +0300
commitd2c42029b98c665725768b891aa877eb3267664f (patch)
treeb232b9a20e45ecb4e7ec2133d7806669054708a6 /src/debug
parentfe39570810969993f51a0ff6045d0a5d4397a5e5 (diff)
downloadplus-d2c42029b98c665725768b891aa877eb3267664f.tar.gz
plus-d2c42029b98c665725768b891aa877eb3267664f.tar.bz2
plus-d2c42029b98c665725768b891aa877eb3267664f.tar.xz
plus-d2c42029b98c665725768b891aa877eb3267664f.zip
Add simple memory leak detector.
Can be enabled by configure option -enable-memdebug=yes. Known issue: not all memory allocations hooked with it.
Diffstat (limited to 'src/debug')
-rw-r--r--src/debug/debug_new.cpp844
-rw-r--r--src/debug/debug_new.h183
-rw-r--r--src/debug/fast_mutex.h314
-rw-r--r--src/debug/static_assert.h53
4 files changed, 1394 insertions, 0 deletions
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 <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
+ * 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 <new>
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef __unix__
+#include <alloca.h>
+#endif
+#ifdef _WIN32
+#include <malloc.h>
+#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 <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 _DEBUG_NEW_PROGNAME
+#define _DEBUG_NEW_PROGNAME NULL
+#endif
+
+/**
+ * @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 _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 <code>operator new</code>.
+ */
+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 <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 = _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, "<Unknown>");
+ }
+}
+
+#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
+ * <code>delete[]</code> 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 <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
+ * 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 <new>
+#include <stdio.h>
+
+/**
+ * @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 <code>operator new</code>, to call
+ * <code>operator new</code> 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 <stdlib.h>
+# 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 <a href="http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/7089382e3bc1c489/85f9107a1dc79ee9?#85f9107a1dc79ee9">Greg Herlihy's post</a> 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.
+ * <code>operator->*</code> 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 <class _Tp> _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 <em>The C++ Programming Language</em> 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 <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
+ * 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 <pthread.h> 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 <stdio.h>
+# include <stdlib.h>
+/** 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 <pthread.h>
+/**
+ * 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 <windows.h>
+/**
+ * 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 <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
+ * 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 <bool> struct __nvwa_compile_time_error;
+template <> struct __nvwa_compile_time_error<true> {};
+
+#define STATIC_ASSERT(_Expr, _Msg) \
+ { \
+ __nvwa_compile_time_error<((_Expr) != 0)> ERROR_##_Msg; \
+ (void)ERROR_##_Msg; \
+ }
+
+#endif // STATIC_ASSERT