blob: ee871c4976b6b890f66032caabeedceb74890f1f [file] [log] [blame]
/************************************************************************
*
* 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;
}