/***************************************************************************
 *
 * 18.support.dynamic.cpp - test exercising [lib.support.dynamic]
 *
 * $Id: 18.support.dynamic.cpp 648752 2008-04-16 17:01:56Z faridz $
 *
 ***************************************************************************
 *
 * 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 2001-2008 Rogue Wave Software.
 *
 **************************************************************************/

#include <new>
#include <memory>
#include <driver.h>

#include <rw/_defs.h>
#include <rw/_error.h>

#include <cstdlib>   // for malloc() and free()
#include <cstring>   // for strlen()


#ifdef _MSC_VER
   // verify that <new> et al can coexist with MSVC's <new.h>
#  include <new.h>
#endif

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

// detects recursive implementation of operator new (size_t, nothrow_t)
static int recursive_new_nothrow = 0;
static int recursive_delete_nothrow = 0;

// MemoryAllocator is used to replace the global new and delete
// in order to test conformance of of the other operators that
// might be defined in <new>

struct MemoryAllocator
{
    MemoryAllocator () {
        init ();
    }

    void* operatorNew (std::size_t n) _THROWS ((std::bad_alloc)) {
        _allocPtr = 0;
        _newCalled = true;
        _bytesRequested = n;
        if (_failAlloc) {
            _failAlloc = false;
            _THROW (std::bad_alloc ());
        }
#if !defined (_RWSTD_NO_EXT_OPERATOR_NEW) && \
     defined (_RWSTD_NO_OPERATOR_NEW_NOTHROW)

        // test our implementation of
        // ::operator new(size_t, const std::nothrow)
        // this operator must not be implemented in terms
        // of ::operator new(size_t) to prevent recursion

        if (recursive_new_nothrow++)
            _allocPtr = std::malloc (_bytesRequested);
        else
            _allocPtr = ::operator new (_bytesRequested, std::nothrow);

        --recursive_new_nothrow;

#else
        _allocPtr = std::malloc (_bytesRequested);
#endif
        return _allocPtr;
    }

    void operatorDelete (void* ptr) _THROWS (()) {
        _deallocPtr = ptr;
#if !defined (_RWSTD_NO_EXT_OPERATOR_NEW) && \
     defined (_RWSTD_NO_OPERATOR_NEW_NOTHROW)
        // memory allocated by our nothrow operator new()
        if (recursive_delete_nothrow++ || recursive_new_nothrow)
            std::free (ptr);
        else
            ::operator delete (ptr, std::nothrow);

        --recursive_delete_nothrow;
#else
        std::free (ptr);
#endif
        _deleteCalled = true;
    }

    void init () {
        _newCalled = _deleteCalled = _failAlloc = false;
        _bytesRequested = 0;
        _deallocPtr = _allocPtr = 0;
    }

    bool newCalled () {
        bool tmp = _newCalled;
        _newCalled = 0;
        return tmp;
    }

    bool deleteCalled () {
        bool tmp = _deleteCalled;
        _deleteCalled = 0;
        return tmp;
    }

    std::size_t  _bytesRequested;
    bool              _newCalled;
    bool              _deleteCalled;
    bool              _failAlloc;
    void*             _allocPtr;
    void*             _deallocPtr;
};


// static instance of memory allocator
static MemoryAllocator alloc;

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


// replace the global operators with MemoryAllocator versions
void* operator new (std::size_t size) _THROWS ((std::bad_alloc))
{
    return alloc.operatorNew (size);
}

void operator delete (void* p) _THROWS (())
{
    alloc.operatorDelete (p);
}

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


#define TEST_OP_THROWS(expr,cond,tag)                \
    _TRY {                                           \
        alloc._failAlloc = true;                     \
        expr;                                        \
        rw_assert (cond, 0, __LINE__, tag);          \
    }                                                \
    _CATCH (std::bad_alloc) {                        \
        RW_ASSERT (!cond, 0, __LINE__, tag);         \
    }                                                \
    _CATCH (...) {                                   \
        rw_assert (!cond, 0, __LINE__, tag);         \
    }                                                \
    (void)0

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


// provide a handler for unexpected exceptions so that at least the
//  test will log the event rather than just aborting
void my_unexpected_handler ()
{
    rw_assert (false, 0, __LINE__, "caught an unexpected exception\n");
}

// used in exists_set_new_handler below
void my_new_handler ()
{
}

//  grab default throw procedure
void (*default_throw_proc)(int, char*) = _RW::__rw_throw_proc;

bool my_throw_proc_called = false;

// my_throw_proc just sets a global flag to indicate
// that it was called, and then dispatches the call
// to the default throw procedure
static void my_throw_proc (int id, char* what)
{
    my_throw_proc_called = true;
    default_throw_proc (id, what);
}

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


static
int run_test (int, char* [])
{
    std::set_unexpected (&my_unexpected_handler);

    if (1) {
        // exists_std_nothrow
        const std::nothrow_t* p = &std::nothrow;
        rw_assert (p != 0, 0, __LINE__, "std::nothrow defined");
    }

    if (1) {
        // exists_bad_alloc
        std::bad_alloc ba1;         // default ctor
        std::bad_alloc ba2 = ba1;   // copy ctor

        std::exception* e = &ba1;   // derived from exception
        (void) &e;
        ba1 = ba2;                  // assignment operator

        // verify that a member function is accessible and has the
        // appropriate signature, including return type and exception
        // specification

        std::bad_alloc& (std::bad_alloc::*p_op_assign)
                (const std::bad_alloc&) _PTR_THROWS (())
            = &std::bad_alloc::operator=;
        _RWSTD_UNUSED (p_op_assign);

        const char*
        (std::bad_alloc::*p_what_fn)() const _PTR_THROWS (())
             = &std::bad_alloc::what;
        _RWSTD_UNUSED (p_what_fn);

        const char* s = ba1.what ();
        rw_assert (s && 0 != std::strlen (s), 0, __LINE__,
                   "std::bad_alloc::what() returned bad string");
    }

    if (1) {
        // set_new_handler
        typedef void (*new_handler_t) ();

        new_handler_t (*f) (new_handler_t) _PTR_THROWS (());
        f = &std::set_new_handler;

        rw_assert (f != 0, 0, __LINE__,
                   "std::set_new_handler() defined");

        new_handler_t original_handler
            = std::set_new_handler (my_new_handler);

#if !defined (__EDG__) || __EDG_VERSION__ > 245 || defined (__DECCXX)

        // EDG eccp 2.45 standalone demo is known to fail
        rw_assert (original_handler == 0, 0, __LINE__,
                   "std::set_new_handler() unexpectedly returned "
                   "installed handler");

#define _RWSTD_NO_EXT_OPERATOR_NEW

#endif   // !__EDG__ || __EDG_VERSION__ > 245 || __DECCXX

        new_handler_t replacement_handler
            = std::set_new_handler (original_handler);
        rw_assert (my_new_handler == replacement_handler, 0, __LINE__,
                   "std::set_new_handler() failed to return previously "
                   "installed handler");
    }

    // test all operators that we have provided definitions for
    // in <new>; exercises the operators by setting the
    // replacement allocator to fail, and asserting that an
    // exception is thrown or not as appropriate for the operator
    if (1) {
        // operator new throws
        void* ptr = 0;
        _RWSTD_UNUSED (ptr);

        alloc.init ();      // reset the allocator

#ifdef _RWSTD_NO_OPERATOR_NEW_NOTHROW

        // verify that the nothrow version of operator new provided
        // by our library doesn't call the ordinary operator new
        // (if it did it would cause recursion if a replacement operator
        // new were to be implemented in terms of the nothrow version)

        // also verify that it returns 0 on failure

        // force a failure by requesting too large a block of storage
        // size_t (~0) alone isn't good enough since it causes annoying
        // dialog boxes to be popped up by the MSVCRTD, the MSVC runtime
        TEST_OP_THROWS ((ptr = operator new (~0U - 4096U, std::nothrow)),
                        (ptr == 0),
                        "operator new(size_t, nothrow_t) returns 0");

        rw_assert (!alloc.newCalled (), 0, __LINE__,
                   "operator new called by operator new(size_t, nothrow)");

#endif // defined _RWSTD_NO_OPERATOR_NEW_NOTHROW

#if defined (_RWSTD_NO_OPERATOR_DELETE_NOTHROW) && \
   !defined (_RWSTD_NO_PLACEMENT_DELETE)

        // verify that the nothrow version of operator delete provided
        // by our library prevents exceptions from propagating
        TEST_OP_THROWS ((operator delete (0, std::nothrow)), true,
                        "operator delete(void*, nothrow_t) doesn't throw");

        // verify that the nothrow version of operator delete provided
        // by our library doesn't call the ordinary operator delete
        // (if it did it might cause recursion if a replacement operator
        // delete were to be implemented in terms of the nothrow version)
        rw_assert (!alloc.deleteCalled (), 0, __LINE__,
                   "operator delete(void*) called by "
                   "operator delete(void*, nothrow_t)");

#endif   // _RWSTD_NO_OPERATOR_DELETE_NOTHROW && _RWSTD_NO_PLACEMENT_DELETE

#ifdef _RWSTD_NO_OPERATOR_NEW_ARRAY

        // verify that the array form of operator new throws bad_alloc on
        // failure
        TEST_OP_THROWS ((operator new[] (1)), false,
                        "operator new[] (size_t) throws bad_alloc");

        // verify that the array form of operator new provided by our
        // library calls the ordinary operator new
        rw_assert (alloc.newCalled (), 0, __LINE__,
                   "operator new called by operator new[](size_t)");

#endif // defined _RWSTD_NO_OPERATOR_NEW_ARRAY

#ifdef _RWSTD_NO_OPERATOR_NEW_ARRAY_NOTHROW

        TEST_OP_THROWS ((ptr = operator new[](1, std::nothrow)),
                        (ptr == 0),
                        "operator new[] (size_t, nothrow_t) returns 0");

        rw_assert (alloc.newCalled (), 0, __LINE__,
                   "operator new(size_t) called by "
                   "operator new[](size_t, nothrow_t)");

#endif // defined _RWSTD_NO_OPERATOR_NEW_ARRAY_NOTHROW

#ifdef _RWSTD_NO_OPERATOR_DELETE_ARRAY

        TEST_OP_THROWS ((operator delete[] (0)), true,
                        "operator delete[] (void*) doesn't throw");

        rw_assert (alloc.deleteCalled (), 0, __LINE__,
                   "operator delete called by operator delete[](void*)");

#endif // defined _RWSTD_NO_OPERATOR_DELETE_ARRAY

#if defined (_RWSTD_NO_OPERATOR_DELETE_ARRAY_NOTHROW) && \
   !defined ( _RWSTD_NO_PLACEMENT_DELETE)

        TEST_OP_THROWS ((operator delete[] (0, std::nothrow)), true,
                        "operator delete[] (void*, nothrow_t) doesn't "
                        "throw");

        rw_assert (alloc.deleteCalled (), 0, __LINE__,
                   "operator delete(void*) called by "
                   "operator delete[](void*, nothrow_t)");

#endif // _RWSTD_NO_OPERATOR_DELETE_ARRAY_NOTHROW &&
        // !_RWSTD_NO_PLACEMENT_DELETE

        // verify that the nothrow form of operator new provided by our
        // library isn't recursively implemented in terms of the ordinary
        // operator new
        rw_assert (0 == recursive_new_nothrow, 0, __LINE__,
                   "operator new (size_t, nothrow_t) causes recursion");

        rw_assert (0 == recursive_delete_nothrow, 0, __LINE__,
                   "operator delete (void*, nothrow_t) causes recursion");
    }

    // __rw_allocate() is like ::operator new() except that it calls
    // __rw_throw() on failure (which may throw std::bad_alloc).
    //  This test checks that __rw_throw() is called,
    //  that it properly calls __rw_throw_proc(), and that bad_alloc
    //  is thrown. We'll use the replaced ::operator new() to
    //  control when a memory allocation request will fail.

#ifndef _RWSTD_NO_REPLACEABLE_NEW_DELETE

    const bool test_rw_allocate = true;

#else   // if defined (_RWSTD_NO_REPLACEABLE_NEW_DELETE)

    // avoid tests that depend on the replacement operators new
    // and delete on platforms like AIX or Win32 where they cannot
    // be reliably replaced
    const bool test_rw_allocate = false;

#endif   // _RWSTD_NO_REPLACEABLE_NEW_DELETE

    if (test_rw_allocate) {

        alloc.init ();      // reset the allocator

        _RW::__rw_throw_proc = my_throw_proc; // set the throw func
        my_throw_proc_called = false;

        for (std::size_t i = 0; i < 10; ++i) {

            char* ptr = 0;

            const bool doFail = !!(i % 2);    // prevent MSVC warning 4800
            if (doFail)
                alloc._failAlloc = true;

            try {
                // allocation
                alloc._bytesRequested = ~i;   // reset

                ptr = _RWSTD_STATIC_CAST (char*, _RW::__rw_allocate (i));
                rw_assert (alloc._bytesRequested == i, 0, __LINE__,
                           "__rw_allocate (%u) requested %u bytes",
                           i, alloc._bytesRequested);

                rw_assert (alloc.newCalled (), 0, __LINE__,
                           "operator new() called");
                rw_assert (!doFail,  0, __LINE__,
                           "allocation succeeded");
                rw_assert (my_throw_proc_called == false,
                           0, __LINE__,
                           "my_throw_proc called");

                // deallocation
                _RW::__rw_deallocate (ptr, i);

                rw_assert (alloc.deleteCalled (),
                           0, __LINE__,
                           "operator delete() called");
                rw_assert (alloc._deallocPtr == ptr,
                           0, __LINE__,
                           "allocated memory deallocated");

            }
            catch (std::bad_alloc) {
                rw_assert (doFail, 0, __LINE__,
                           "expected bad_alloc exception");

                rw_assert (my_throw_proc_called == true, 0, __LINE__,
                           "my_throw_proc called");
            }
            catch (...) {
                rw_assert (false, 0, __LINE__,
                           "expected std::bad_alloc exception");
            }

            my_throw_proc_called = false;
        }
    }
    else {
        rw_warn (false, 0, __LINE__,
                 "__rw::__rw_allocate() not exercised: "
                 "_RWSTD_NO_REPLACEABLE_NEW_DELETE #defined");
    }

    return 0;
}

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


int main (int argc, char* argv [])
{
    return rw_test (argc, argv, __FILE__,
                    "lib.support.dynamic",
                    0 /* no comment */,
                    run_test,
                    "",
                    (void*)0);
}
