Click here to Skip to main content
15,888,803 members
Articles / Web Development / HTML

QxOrm - C++ ORM (Object Relational Mapping) Library

Rate me:
Please Sign up or sign in to vote.
4.90/5 (61 votes)
24 Apr 2019GPL326 min read 140.8K   321   140  
QxOrm C++ library: Persistence (based on QtSql Qt library) - Serialization (based on boost::serialization library) - Reflection (introspection)
// -*- 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
 *
 */

/**
 * @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 <QxCommon/QxConfig.h>
#include <QxCommon/QxMacro.h>

#ifndef NDEBUG
#ifndef QT_NO_DEBUG
#if _QX_USE_MEM_LEAK_DETECTION

#include <new>
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#ifdef __unix__
#include <alloca.h>
#endif

#ifdef _WIN32
#include <windows.h>
#include <malloc.h>
#endif

#include <QtCore/qglobal.h>

#include <QxMemLeak/fast_mutex.h>
#include <QxMemLeak/static_assert.h>

#ifndef _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE
#define _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE 0
#endif // _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE

#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_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 <QxMemLeak/debug_new.h>

/**
 * Gets the aligned value of memory block size.
 */
#define align(s) \
        (((s) + _DEBUG_NEW_ALIGNMENT - 1) & ~(_DEBUG_NEW_ALIGNMENT - 1))

namespace qx {
namespace memory {

/**
 * 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_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);
        qDebug("[QxOrm] Out of memory when allocating %u bytes", 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)
    {
        QX_STRNCPY(ptr->file, file, _DEBUG_NEW_FILENAME_LEN - 1);
        ptr->file[_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);
        const char * msg_out_file = "[QxOrm] new%s: allocated %p (size %u, %s:%d)"; Q_UNUSED(msg_out_file);
        const char * msg_out_no_file = "[QxOrm] new%s: allocated %p (size %u, <Unknown>)"; Q_UNUSED(msg_out_no_file);
        if (line != 0) { qDebug(msg_out_file, (is_array ? "[]" : ""), pointer, size, ptr->file, ptr->line); }
#if (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
        else { qDebug(msg_out_no_file, (is_array ? "[]" : ""), pointer, size); }
#endif // (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
    }
    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
 */
void __debug_new_recorder::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);
            const char * msg_out = "[QxOrm] delete%s: invalid pointer %p"; Q_UNUSED(msg_out);
            qDebug(msg_out, (is_array ? "[]" : ""), pointer);
        }
        check_mem_corruption();
        fflush(new_output_fp);
        _DEBUG_NEW_ERROR_ACTION;
    }
    if (is_array != ptr->is_array)
    {
        const char * msg;
        if (is_array) { msg = "[QxOrm] delete[] after new"; }
        else { msg = "[QxOrm] delete after new[]"; }
        fast_mutex_autolock lock(new_output_lock);
        const char * msg_out_file = "[QxOrm] %s: pointer %p (size %u)\n\tat %p\n\toriginally allocated at %s:%d"; Q_UNUSED(msg_out_file);
        const char * msg_out_no_file = "[QxOrm] %s: pointer %p (size %u)\n\tat %p\n\toriginally allocated at <Unknown>"; Q_UNUSED(msg_out_no_file);
        if (ptr->line != 0) { qDebug(msg_out_file, msg, ((char*)ptr + ALIGNED_LIST_ITEM_SIZE), ptr->size, addr, ptr->file, ptr->line); }
#if (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
        else { qDebug(msg_out_no_file, msg, ((char*)ptr + ALIGNED_LIST_ITEM_SIZE), ptr->size, addr); }
#endif // (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
        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);
        const char * msg_out = "[QxOrm] delete%s: freed %p (size %u, %u bytes still allocated)"; Q_UNUSED(msg_out);
        qDebug(msg_out, (is_array ? "[]" : ""), ((char*)ptr + ALIGNED_LIST_ITEM_SIZE), ptr->size, 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)
        {
            const char * msg_out = "[QxOrm] warning: heap data corrupt near %p"; Q_UNUSED(msg_out);
            qDebug(msg_out, pointer);
        }
#if _DEBUG_NEW_TAILCHECK
        if (!check_tail(ptr))
        {
            const char * msg_out = "[QxOrm] warning: overwritten past end of object at %p"; Q_UNUSED(msg_out);
            qDebug(msg_out, pointer);
        }
#endif
        const char * msg_out_file = "[QxOrm] Leaked object at %p (size %u, %s:%d)"; Q_UNUSED(msg_out_file);
        const char * msg_out_no_file = "[QxOrm] Leaked object at %p (size %u, <Unknown>)"; Q_UNUSED(msg_out_no_file);
        if (ptr->line != 0) { qDebug(msg_out_file, pointer, ptr->size, ptr->file, ptr->line); ++leak_cnt; }
#if (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
        else { qDebug(msg_out_no_file, pointer, ptr->size); ++leak_cnt; }
#endif // (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
        ptr = ptr->next;
    }
    if (new_verbose_flag || leak_cnt)
    {
        const char * msg_out_leak_cnt = "[QxOrm] **** %d memory leaks found ****"; Q_UNUSED(msg_out_leak_cnt);
        qDebug(msg_out_leak_cnt, 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);
    const char * msg_out_check_mem_start = "[QxOrm] **** Checking for memory corruption: START ****"; Q_UNUSED(msg_out_check_mem_start);
    qDebug("%s", msg_out_check_mem_start);
    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
            const char * msg_out_file = "[QxOrm] Heap data corrupt near %p (size %u, %s:%d)"; Q_UNUSED(msg_out_file);
            const char * msg_out_no_file = "[QxOrm] Heap data corrupt near %p (size %u, <Unknown>)"; Q_UNUSED(msg_out_no_file);
            if (ptr->line != 0) { qDebug(msg_out_file, pointer, ptr->size, ptr->file, ptr->line); ++corrupt_cnt; }
#if (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
            else { qDebug(msg_out_no_file, pointer, ptr->size); ++corrupt_cnt; }
#endif // (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
#if _DEBUG_NEW_TAILCHECK
        }
        else
        {
            const char * msg_out_file = "[QxOrm] Overwritten past end of object at %p (size %u, %s:%d)"; Q_UNUSED(msg_out_file);
            const char * msg_out_no_file = "[QxOrm] Overwritten past end of object at %p (size %u, <Unknown>)"; Q_UNUSED(msg_out_no_file);
            if (ptr->line != 0) { qDebug(msg_out_file, pointer, ptr->size, ptr->file, ptr->line); ++corrupt_cnt; }
#if (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
            else { qDebug(msg_out_no_file, pointer, ptr->size); ++corrupt_cnt; }
#endif // (! _QX_MEM_LEAK_ONLY_KNOWN_SRC_FILE)
        }
#endif
    }
    const char * msg_out_check_mem_end = "[QxOrm] **** Checking for memory corruption: %d FOUND ****"; Q_UNUSED(msg_out_check_mem_end);
    qDebug(msg_out_check_mem_end, 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);
        const char * msg_out = "[QxOrm] warning: debug_new used with placement new (%s:%d)"; Q_UNUSED(msg_out);
        qDebug(msg_out, _M_file, _M_line);
        return;
    }
#if _DEBUG_NEW_FILENAME_LEN == 0
    ptr->file = _M_file;
#else
    QX_STRNCPY(ptr->file, _M_file, _DEBUG_NEW_FILENAME_LEN - 1);
    ptr->file[_DEBUG_NEW_FILENAME_LEN - 1] = '\0';
#endif
    ptr->line = _M_line;
}

int __debug_new_counter::_S_count = 0;
__debug_new_counter::__debug_new_counter() { ++_S_count; }
__debug_new_counter::~__debug_new_counter() { if ((--_S_count == 0) && new_autocheck_flag) { check_leaks(); } }

} // namespace memory
} // namespace qx

void* operator new(size_t size, const char* file, int line)
{
    void* ptr = qx::memory::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 = qx::memory::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
}

#if HAVE_PLACEMENT_DELETE
void operator delete(void* pointer, const char* file, int line) throw()
{
    if (qx::memory::new_verbose_flag)
    {
        qx::memory::fast_mutex_autolock lock(qx::memory::new_output_lock);
        const char * msg_out = "[QxOrm] info: exception thrown on initializing object at %p (%s:%d)"; Q_UNUSED(msg_out);
        qDebug(msg_out, pointer, file, line);
    }
    operator delete(pointer);
}

void operator delete[](void* pointer, const char* file, int line) throw()
{
    if (qx::memory::new_verbose_flag)
    {
        qx::memory::fast_mutex_autolock lock(qx::memory::new_output_lock);
        const char * msg_out = "[QxOrm] info: exception thrown on initializing objects at %p (%s:%d)"; Q_UNUSED(msg_out);
        qDebug(msg_out, pointer, file, line);
    }
    operator delete[](pointer);
}
#endif // HAVE_PLACEMENT_DELETE

#endif // _QX_USE_MEM_LEAK_DETECTION
#endif // QT_NO_DEBUG
#endif // NDEBUG

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions