blob: 07a0adb4d10223c13c44c38cc6a1edde40fa0aff [file] [log] [blame]
/***************************************************************************
*
* 18.support.dynamic.cpp - test exercising [lib.support.dynamic]
*
* $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 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);
}