/************************************************************************
 *
 * new.cpp - definitions of replacement operator new and delete
 *
 * $Id$
 *
 ************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 2003-2006 Rogue Wave Software.
 * 
 **************************************************************************/

// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC

#include <stdlib.h>   // for abort(), getenv()
#include <string.h>   // for memset()

#include <rw_driver.h>
#include <rw_printf.h>
#include <rw_new.h>
#include <rw_exception.h>   // for rw_throw()

/************************************************************************/

#ifndef _RWSTD_BAD_ALLOC
   // _RWSTD_BAD_ALLOC is #defined in <new> but some compilers (e.g.,
   // SunPro) insist on #including their own new no matter what the
   // preprocessor path or what command line options are used
#  if    !defined (_RWSTD_NO_STD_BAD_ALLOC) \
      || !defined (_RWSTD_NO_RUNTIME_IN_STD)
#    define _RWSTD_BAD_ALLOC _STD::bad_alloc
  #else   // if _RWSTD_NO_STD_BAD_ALLOC && _RWSTD_NO_RUNTIME_IN_STD

     // working around a gcc 2.x bug (PR #24400)
#    if !defined (__GNUG__) || __GNUG__ > 2

#      define _RWSTD_BAD_ALLOC _STD::bad_alloc
#    else
#      define _RWSTD_BAD_ALLOC ::bad_alloc
#    endif   // gcc > 2.x
#  endif   // _RWSTD_NO_STD_BAD_ALLOC || !_RWSTD_NO_RUNTIME_IN_STD
#endif   // _RWSTD_BAD_ALLOC

#ifndef _RWSTD_BAD_ALLOC
#  ifndef _RWSTD_NO_RUNTIME_IN_STD
#    define _RWSTD_BAD_ALLOC std::bad_alloc
#  else
#    define _RWSTD_BAD_ALLOC bad_alloc
#  endif   // _RWSTD_NO_RUNTIME_IN_STD
#endif   // _RWSTD_BAD_ALLOC

/************************************************************************/

// keeps track of the number of pending calls to global dtors
static size_t static_dtors;

static size_t throw_at_calls [2] = { _RWSTD_SIZE_MAX, _RWSTD_SIZE_MAX };
static size_t throw_at_blocks [2] = { _RWSTD_SIZE_MAX, _RWSTD_SIZE_MAX };
static size_t throw_at_bytes [2] = { _RWSTD_SIZE_MAX, _RWSTD_SIZE_MAX };
static size_t break_at_seqno = _RWSTD_SIZE_MAX;

// enables tracing to stderr between the two sequence numbers
static size_t trace_sequence [2] = { 0, 0 };

static rwt_free_store store = {
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { throw_at_calls, throw_at_calls + 1 },
    { throw_at_blocks, throw_at_blocks + 1 },
    { throw_at_bytes, throw_at_bytes + 1 },
    &break_at_seqno
};

static rwt_free_store* pst = &store;


struct Header
{
    Header *prev_;   // link to the previous allocated block
    Header *next_;   // link to the next allocated block
    void   *ptr_;    // pointer to user block (== this + 1)
    size_t  size_;   // size of user block in bytes
    size_t  id_;     // unique id
    void*   self_;   // padded to an even multiple of sizeof(size_t)
                     // with the value of this
};


static Header *last;   // pointer to the most recently allocated block


// guard block stored immediately after the end of the user block
static const char Guard [8] = {
    '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff'
};


static void
_rw_print_heap ();

// find a block by the user pointer in the list of allocated blocks
static Header*
_rw_find_block (void *ptr, bool check_heap, const char *caller)
{
    size_t nblocks = 0;
    size_t nbytes  = 0;

    Header *res = 0;

    for (Header *hdr = last; hdr; hdr = hdr->prev_) {

        nblocks += 1;
        nbytes  += hdr->size_;

        if (check_heap) {
            // check the lead guard
            if (hdr->self_ != hdr) {
                rw_error (0, 0, __LINE__,
                          "%s:%d: header guard corruption at %#p: "
                          "expected %#p, got %#p",
                          hdr->ptr_, (const void*)hdr, hdr->self_);
                abort ();
            }

            // check that the stored pointer has the expected value
            if (hdr->ptr_ != hdr + 1) {
                rw_error (0, 0, __LINE__,
                          "%s:%d: block address mismatch: "
                          "expected %#p, got %#p",
                          __FILE__, __LINE__,
                          (const void*)(hdr + 1), hdr->ptr_);

                abort ();
            }

            // check the trailing guard
            const char* const grd = (char*)hdr->ptr_ + hdr->size_;

            if (memcmp (grd, Guard, sizeof Guard)) {

                size_t off = 0;
                while (grd [off] == Guard [off])
                    ++off;

                typedef unsigned char UChar;

                rw_error (0, 0, __LINE__,
                          "%s:%d: trailing guard corruption at %#p "
                          "+ %zu of a %zu byte block: '0x%02x' != '0x%x'",
                          __FILE__, __LINE__, hdr->ptr_,
                          hdr->size_ + off + 1, hdr->size_,
                          UChar (grd [off]), UChar (Guard [off]));

                abort ();
            }
        }

        if (ptr == hdr->ptr_) {
            res = hdr;

            if (!check_heap)
                return res;
        }
    }

    if (check_heap) {
        // check that block and byte counters match the totals computed above
        const size_t sum_blocks = pst->blocks_ [0] + pst->blocks_ [1];
        const size_t sum_bytes  = pst->bytes_ [0] + pst->bytes_ [1];

        if (sum_blocks != nblocks || sum_bytes != nbytes) {
            rw_error (0, 0, __LINE__,
                      "%s:%d: counts mismatch: found %zu "
                      "bytes in %zu blocks, expected "
                      "%zu in %zu", __FILE__, __LINE__, 
                      nbytes, nblocks, sum_bytes, sum_blocks);

            abort ();
        }
    }

    if (caller && ptr && !res) {

#if !defined (__DECCXX_VER) || __DECCXX_VER >= 60600000

        rw_error (0, 0, __LINE__,  
                  "%s:%d: %s (%#p): invalid pointer",
                  __FILE__, __LINE__, caller, ptr);

        _rw_print_heap ();

        abort ();

#else   // Compaq C++ < 6.6

        // working around a bug in Compaq C++ libcxx
        // Classic Iostreams library (see bug #359)
        if (static_dtors) {

            rw_error (0, 0, __LINE__, 
                      "%s:%d: %s (%#p): invalid pointer",
                      __FILE__, __LINE__, caller, ptr);

            _rw_print_heap ();

            abort ();
        }
        else {

            static int warned;

            rw_warn (0 < warned++, 0, __LINE__,
                     "%s:%d: %s (%#p): warning: invalid pointer; "
                     "ignoring memory errors from here on out",
                     __FILE__, __LINE__, caller, ptr);
        }

#endif   // Compaq C++ >= 6.6

    }

    return res;
}


// print the list of allocated blocks and check heap consistency
static void
_rw_print_heap ()
{
    rw_info (0, 0, __LINE__,
             "%s:%d: heap dump:\n%zu bytes in %zu blocks%s",
             __FILE__, __LINE__, pst->bytes_, pst->blocks_, last ? ":" : "");

    for (Header *hdr = last; hdr; hdr = hdr->prev_) {

        const size_t seq = hdr->id_;
        const bool array = !!(seq >> (_RWSTD_CHAR_BIT * sizeof (size_t) - 1));

        rw_info (0, 0, __LINE__, 
                 "%zu: %zu bytes at %#p allocated by operator new%s()",
                 hdr->id_, hdr->size_, hdr->ptr_, array ? "[]" : "");
    }

    if (last) 
        // check heap consistency
        _rw_find_block (last, true, 0);
}


static size_t seq_gen;   // sequence number generator


_TEST_EXPORT void*
operator_new (size_t nbytes, bool array)
{
    if (0 == trace_sequence [0] && trace_sequence [0] == trace_sequence [1]) {

        // the first time opetato new is called try to get options
        // from the environment by checking the RWSTD_NEW_FLAGS
        // environment variable in the following format:
        //
        // RWSTD_NEW_FLAGS=[<seqrange>][:<break-seqno>]
        // seqrange ::= <start-seqno>[-<end-seqno>]

        static const char* envvar = getenv ("RWSTD_NEW_FLAGS");

        if (envvar) {
            char *end = _RWSTD_CONST_CAST (char*, envvar);

            if ('-' == *end) {
                // begin tracing with the sequence number 0
                trace_sequence [0] = 0;
            }
            else {
                // begin tracing with the given sequence number
                trace_sequence [0] = strtoul (end, &end, 10);
            }

            if ('-' == *end) {
                // end tracing with the given sequence number
                ++end;
                trace_sequence [1] = strtoul (end, &end, 10);
            }
            else {
                // continue tracing indefinitely
                trace_sequence [1] = _RWSTD_SIZE_MAX;
            }

            if (':' == *end) {
                // break at the given seuqence number
                ++end;
                *pst->break_at_seqno_ = strtoul (end, &end, 10);
            }
        }
    }

    static const char* const name[] = {
        "operator new", "operator new[]"
    };

    // increment the call counter regardless of success
    ++pst->new_calls_ [array];

    // prevent warnings about unused variable and/or unreachable statement
    void *ptr = 0;

    const bool reached_call_limit =
        pst->new_calls_ [array] == *pst->throw_at_calls_ [array];
    const bool reached_block_limit =
        pst->blocks_ [array] >= *pst->throw_at_blocks_ [array];
    const bool reached_size_limit =
        pst->bytes_ [array] + nbytes >= *pst->throw_at_bytes_ [array];

    const bool reached_breakpoint = seq_gen == *pst->break_at_seqno_;

    if (reached_breakpoint) {
        char buffer [128];
        rw_snprintfa (buffer, 128, 
                      "%s (%zu): breakpoint at sequence number %zu",
                      name [array], nbytes, *pst->break_at_seqno_);

        // abort() when a breakpoint has been reached
        _RW::__rw_assert_fail (buffer, __FILE__, __LINE__, 0);

        // should not get here except when the program handles
        // SIGABRT and returns from the handler
    }

    if (   reached_call_limit
        || reached_block_limit
        || reached_size_limit) {

#ifndef _RWSTD_NO_EXCEPTIONS

        try {
            const char * threw = "threw bad_alloc:";

            if (reached_call_limit)
                rw_throw (ex_bad_alloc,
                __FILE__, __LINE__, name [array], 
                "(%zu) %s reached call limit of %zu", 
                nbytes, threw, pst->new_calls_ [array]);

            else if (reached_block_limit)
                rw_throw (ex_bad_alloc,
                __FILE__, __LINE__, name [array], 
                "(%zu) %s reached block limit of %zu: %zu",
                nbytes, threw, *pst->throw_at_blocks_ [array], 
                pst->blocks_ [array]);

            else if (reached_size_limit)
                rw_throw (ex_bad_alloc,
                __FILE__, __LINE__, name [array], 
                "(%zu) %s reached size limit of %zu: %zu",
                nbytes, threw, *pst->throw_at_bytes_ [array], 
                pst->bytes_ [array]);
        }
        catch (const std::exception & ex) {
            if (trace_sequence [0] <= seq_gen && seq_gen < trace_sequence [1])
                rw_fprintf (rw_stderr, "%s\n", ex.what ());

            throw;
        }

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

        if (reached_breakpoint) {
            char buf[4096];

            rw_snprintfa (buf, sizeof(buf),
                "reached a breakpoint at of %zu calls", *pst->break_at_seqno_);

            _RW::__rw_assert_fail (buf, __FILE__, __LINE__, name [array]);
        }

        if (trace_sequence [0] <= seq_gen && seq_gen < trace_sequence [1])
            rw_fprintf (rw_stderr, "%s:%d: %s (%zu) --> %#p\n",
                        __FILE__, __LINE__, name [array], nbytes, ptr);

        return 0;

#endif   // _RWSTD_NO_EXCEPTIONS

    }

    const size_t block_size = nbytes + sizeof (Header) + sizeof (Guard);

    // prevent arithmetic overflow
    if (nbytes < block_size)
        ptr = malloc (block_size);

    if (!ptr) {

#ifndef _RWSTD_NO_EXCEPTIONS

        try {
            rw_throw (ex_bad_alloc,
                __FILE__, __LINE__, name [array], 
                "(%zu): malloc() returned 0", nbytes);
        }
        catch (const std::exception & ex) {
            if (trace_sequence [0] <= seq_gen && seq_gen < trace_sequence [1])
                rw_fprintf (rw_stderr, "%s\n", ex.what ());

            throw;
        }

#else   // if defined (_RWSTD_NO_EXCEPTIONS)
        return ptr;
#endif   // _RWSTD_NO_EXCEPTIONS
    }

    // invalidate storage
    memset (ptr, -1, block_size);

    // increment counters
    pst->blocks_ [array] += 1;
    pst->bytes_ [array]  += nbytes;

    // adjust the maximum total number of blocks ever allocated
    if (pst->blocks_ [array] > pst->max_blocks_ [array])
        pst->max_blocks_ [array] = pst->blocks_ [array];

    // adjust the maximum total number of bytes ever allocated
    if (pst->bytes_ [array] > pst->max_bytes_ [array])
        pst->max_bytes_ [array] = pst->bytes_ [array];

    // adjust the size of the single largest block ever allocated
    if (nbytes > pst->max_block_size_ [array])
        pst->max_block_size_ [array] = nbytes;

    // copy guard to the end of the allocated block
    memcpy ((char*)ptr + sizeof (Header) + nbytes, Guard, sizeof (Guard));

    Header* const hdr = (Header*)ptr;

    hdr->ptr_  = (Header*)ptr + 1;
    hdr->size_ = nbytes;
    hdr->id_   = array ? ~seq_gen : seq_gen;
    hdr->self_ = hdr;

    if (0 == last) {
        hdr->prev_ = 0;
        hdr->next_ = 0;
    }
    else {
        hdr->next_  = 0;
        hdr->prev_  = last;
        last->next_ = hdr;
    }

    last = hdr;

    if (trace_sequence [0] <= seq_gen && seq_gen < trace_sequence [1])
        rw_note (0, 0, __LINE__,
                 "%s:%d: %3zi. %s (%zu) --> %#p", 
                 __FILE__, __LINE__, seq_gen, 
                 name [array], nbytes, hdr->ptr_);

    ++seq_gen;

    _rw_find_block (hdr->ptr_, true, 0);

    return hdr->ptr_;
}


_TEST_EXPORT void
operator_delete (void *ptr, bool array)
{
    static const char* const name[] = {
        "operator delete", "operator delete[]"
    };

    // increment the call counter regardless of success
    ++pst->delete_calls_ [array];

    if (ptr) {

        // find the block of memory that `ptr' was allocated from
        // and check the whole heap in the process; the call will
        // abort if any block has been corrupted
        Header* const hdr = _rw_find_block (ptr, true, name [array]);

        if (!hdr) {
            // hdr should never be 0 except under special circumstances
            // such as when the compiler's runtime library itself passes
            // the wrong argument to operator delete (such as libcxx
            // on True64 with Compaq C++ -- see bug #359)
            free (ptr);

            return;
        }

        const size_t nbytes = hdr->size_;
        
        bool mismatch;

        size_t seq = hdr->id_;
        if (seq >> (_RWSTD_CHAR_BIT * sizeof (size_t) - 1)) {

            // the MSB of the stored sequence number is set
            // for blocks allocated with the array form of
            // operator new and must be deallocated with
            // the corresponding array form of operator
            // delete
            mismatch = !array;
            seq      = ~seq;
        }
        else {
            mismatch = array;
        }

        if (trace_sequence [0] <= seq && seq < trace_sequence [1]) {
            if (mismatch)
                rw_error (0, 0, __LINE__, "%s:%d: %3zi. %s (%#p); size = %zu"
                          ": array form mismatch",
                          __FILE__, __LINE__, seq, name [array], ptr, nbytes);
            else
                rw_note (0, 0, __LINE__, "%s:%d: %3zi. %s (%#p); size = %zu",
                         __FILE__, __LINE__, seq, name [array], ptr, nbytes);
        }
        else if (mismatch) {

            const size_t ord = (seq + 1) % 10;

            const char* const ord_sfx =
                1 == ord ? "st" : 2 == ord ? "nd" : 3 == ord ? "rd" : "th";

            rw_error (0, 0, __LINE__,
                      "%s:%d: deallocation mismatch: "
                      "pointer allocated %zu%s in the program "
                      "with a call to operator new%s(%zu) "
                      "being deallocated with the wrong form of %s(%#p)",
                      __FILE__, __LINE__,
                      seq + 1, ord_sfx, array ? "" : "[]",
                      nbytes, name [array], ptr);
            abort ();
        }

        // decrement block and byte counters and remove block
        // from the list only after all checks have succeeded
        // so that tests that catch SIGABRT sent by one of the
        // calls to abort() above and retry the operation may
        // succeed
        pst->blocks_ [array] -= 1;
        pst->bytes_ [array]  -= nbytes;

        if (hdr->prev_)
            hdr->prev_->next_ = hdr->next_;
        if (hdr->next_)
            hdr->next_->prev_ = hdr->prev_;

        if (hdr == last)
            last = hdr->prev_;

        const size_t block_size = nbytes + sizeof (Header) + sizeof (Guard);
        
        // invalidate the entire block including bookkeeping data
        memset (hdr, -1, block_size);

        free (hdr);
    }
    else {
        ++pst->delete_0_calls_ [array];

        if (trace_sequence [0] <= seq_gen && seq_gen < trace_sequence [1])
            rw_note (0, 0, __LINE__, "%s:%d: %s (0)", 
                     __FILE__, __LINE__, name [array]);
    }
}

_TEST_EXPORT rwt_free_store*
rwt_get_free_store (rwt_free_store *st)
{
    rwt_free_store* const ret = pst;

    if (st)
        pst = st;

    return ret;
}

static void
rwt_checkpoint_compare (size_t* diffs, size_t n,
                        const size_t* st0, const size_t* st1,
                        bool& diff_0)
{
    for (size_t i = 0; i != n; ++i) {
        diffs [i] = st1 [i] - st0 [i];
        if (diffs [i])
            diff_0 = false;
    }
}

_TEST_EXPORT rwt_free_store*
rwt_checkpoint (const rwt_free_store *st0, const rwt_free_store *st1)
{
    static rwt_free_store checkpoint;

    if (st0 && st1) {

        // compute the difference between the two states
        // of the free_store specified by the arguments

        static rwt_free_store diff;
        memset (&diff, 0, sizeof diff);

        bool diff_0 = true;   // difference of 0 (i.e., none)

#define EXTENT(array) (sizeof (array) / sizeof(*array))
#define COMPARE(member) \
        rwt_checkpoint_compare (diff.member, EXTENT(diff.member), \
                                st0->member, st1->member, diff_0)

        COMPARE (new_calls_);
        COMPARE (delete_calls_);
        COMPARE (delete_0_calls_);
        COMPARE (blocks_);
        COMPARE (bytes_);
        COMPARE (max_blocks_);
        COMPARE (max_bytes_);
        COMPARE (max_block_size_);

        if (diff_0)
            return 0;

        return &diff;
    }

    if (!st0 && !st1) {
        // compute the difference between the most recent checkpoint
        // and the current state of the free store; store the current
        // state as the new checkpoint

        _rw_find_block (0, true, 0);

        rwt_free_store* const ckpt = rwt_checkpoint (&checkpoint, pst);

        memcpy (&checkpoint, pst, sizeof checkpoint);

        return ckpt;
    }

    if (st0) {
        // compute the difference between the checkpoint specified by
        // the first argument and the current state of the free store
        // store the current state as the last checkpoint

        return rwt_checkpoint (st0, pst);
    }

    // compute the difference between the most recent checkpoint and
    // the checkpoint specified by the second argument

    return rwt_checkpoint (&checkpoint, st1);
}


_TEST_EXPORT _RWSTD_SIZE_T
rwt_check_leaks (_RWSTD_SIZE_T *bytes, const rwt_free_store *st)
{
    const rwt_free_store* const ckpt = rwt_checkpoint (st, 0);

    if (ckpt) {
        if (bytes)
            *bytes = ckpt->bytes_ [0] + ckpt->bytes_ [1];

        return ckpt->blocks_ [0] + ckpt->blocks_ [1];
    }

    if (bytes)
        *bytes = 0;

    return 0;
}


MyNewInit::MyNewInit ()
{
    ++static_dtors;
}

MyNewInit::~MyNewInit ()
{
    --static_dtors;
}
