/************************************************************************************************ NC_ALLOC.CPP * Copyright (c) 1997 * Mark of the Unicorn, Inc. * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Mark of the Unicorn makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. ************************************************************************************************/ #include "nc_alloc.h" #include <string> #if defined (EH_NEW_HEADERS) # include <new> # include <cassert> # include <cstdlib> #else # include <assert.h> # include <stdlib.h> # include <new.h> #endif #if defined (EH_NEW_IOSTREAMS) # include <iostream> #else # include <iostream.h> #endif long alloc_count = 0; long object_count = 0; long TestController::possible_failure_count = 0; const char* TestController::current_test = "<unknown>"; const char* TestController::current_test_category = "no category"; const char* TestController::current_container = 0; bool TestController::nc_verbose = true; bool TestController::never_fail = false; bool TestController::track_allocations = false; bool TestController::leak_detection_enabled = false; TestController gTestController; //************************************************************************************************ void TestController::maybe_fail(long) { if (never_fail || Failure_threshold() == kNotInExceptionTest) return; // throw if allocation would satisfy the threshold if (possible_failure_count++ >= Failure_threshold()) { // what about doing some standard new_handler() behavior here (to test it!) ??? // reset and simulate an out-of-memory failure Failure_threshold() = kNotInExceptionTest; #ifndef EH_NO_EXCEPTIONS throw EH_STD::bad_alloc(); #endif } } #if defined (EH_HASHED_CONTAINERS_IMPLEMENTED) # if defined (__SGI_STL) # if defined (EH_NEW_HEADERS) # include <hash_set> # else # include <hash_set.h> # endif # elif defined (__MSL__) # include <hashset.h> # else # error what do I include to get hash_set? # endif #else # if defined (EH_NEW_HEADERS) # include <set> # else # include <set.h> # endif #endif #if !defined (EH_HASHED_CONTAINERS_IMPLEMENTED) typedef EH_STD::set<void*, EH_STD::less<void*> > allocation_set; #else USING_CSTD_NAME(size_t) struct hash_void { size_t operator()(void* x) const { return (size_t)x; } }; typedef EH_STD::hash_set<void*, ::hash_void, EH_STD::equal_to<void*> > allocation_set; #endif static allocation_set& alloc_set() { static allocation_set s; return s; } // Prevents infinite recursion during allocation static bool using_alloc_set = false; #if !defined (NO_FAST_ALLOCATOR) // // FastAllocator -- speeds up construction of TestClass objects when // TESTCLASS_DEEP_DATA is enabled, and speeds up tracking of allocations // when the suite is run with the -t option. // class FastAllocator { public: //FastAllocator() : mFree(0), mUsed(0) {} static void *Allocate(size_t s) { void *result = 0; if (s <= sizeof(Block)) { if (mFree != 0) { result = mFree; mFree = mFree->next; } else if (mBlocks != 0 && mUsed < kBlockCount) { result = (void*)&mBlocks[mUsed++]; } } return result; } static bool Free(void* p) { Block* b = (Block*)p; if (mBlocks == 0 || b < mBlocks || b >= mBlocks + kBlockCount) return false; b->next = mFree; mFree = b; return true; } struct Block; friend struct Block; enum { // Number of fast allocation blocks to create. kBlockCount = 1500, // You may need to adjust this number for your platform. // A good choice will speed tests. A bad choice will still work. kMinBlockSize = 48 }; struct Block { union { Block *next; double dummy; // fbp - force alignment char dummy2[kMinBlockSize]; }; }; static Block* mBlocks; static Block *mFree; static size_t mUsed; }; FastAllocator::Block *FastAllocator::mBlocks = (FastAllocator::Block*)EH_CSTD::calloc( sizeof(FastAllocator::Block), FastAllocator::kBlockCount ); FastAllocator::Block *FastAllocator::mFree; size_t FastAllocator::mUsed; static FastAllocator gFastAllocator; #endif inline char* AllocateBlock(size_t s) { #if !defined (NO_FAST_ALLOCATOR) char * const p = (char*)gFastAllocator.Allocate( s ); if (p != 0) return p; #endif return (char*)EH_CSTD::malloc(s); } static void* OperatorNew( size_t s ) { if (!using_alloc_set) { simulate_possible_failure(); ++alloc_count; } char *p = AllocateBlock(s); if (gTestController.TrackingEnabled() && gTestController.LeakDetectionEnabled() && !using_alloc_set) { using_alloc_set = true; bool inserted = alloc_set().insert(p).second; // Suppress warning about unused variable. inserted; EH_ASSERT(inserted); using_alloc_set = false; } return p; } void* _STLP_CALL operator new(size_t s) #ifdef EH_DELETE_HAS_THROW_SPEC throw(EH_STD::bad_alloc) #endif { return OperatorNew( s ); } #ifdef EH_USE_NOTHROW void* _STLP_CALL operator new(size_t size, const EH_STD::nothrow_t&) throw() { try { return OperatorNew( size ); } catch (...) { return 0; } } #endif #if 1 /* defined (EH_VECTOR_OPERATOR_NEW) */ void* _STLP_CALL operator new[](size_t size ) throw(EH_STD::bad_alloc) { return OperatorNew( size ); } # ifdef EH_USE_NOTHROW void* _STLP_CALL operator new[](size_t size, const EH_STD::nothrow_t&) throw() { try { return OperatorNew(size); } catch (...) { return 0; } } # endif void _STLP_CALL operator delete[](void* ptr) throw() { operator delete( ptr ); } #endif #if defined (EH_DELETE_HAS_THROW_SPEC) void _STLP_CALL operator delete(void* s) throw() #else void _STLP_CALL operator delete(void* s) #endif { if ( s != 0 ) { if ( !using_alloc_set ) { --alloc_count; if ( gTestController.TrackingEnabled() && gTestController.LeakDetectionEnabled() ) { using_alloc_set = true; allocation_set::iterator p = alloc_set().find( (char*)s ); EH_ASSERT( p != alloc_set().end() ); alloc_set().erase( p ); using_alloc_set = false; } } # if ! defined (NO_FAST_ALLOCATOR) if ( !gFastAllocator.Free( s ) ) # endif EH_CSTD::free(s); } } /*=================================================================================== ClearAllocationSet (private helper) EFFECTS: Empty the set of allocated blocks. ====================================================================================*/ void TestController::ClearAllocationSet() { if (!using_alloc_set) { using_alloc_set = true; alloc_set().clear(); using_alloc_set = false; } } bool TestController::ReportLeaked() { EndLeakDetection(); EH_ASSERT( !using_alloc_set || (alloc_count == static_cast<int>(alloc_set().size())) ); if (alloc_count != 0 || object_count != 0) { EH_STD::cerr<<"\nEH TEST FAILURE !\n"; PrintTestName(true); if (alloc_count) EH_STD::cerr << "ERROR : " << alloc_count << " outstanding allocations.\n"; if (object_count) EH_STD::cerr << "ERROR : " << object_count << " non-destroyed objects.\n"; alloc_count = object_count = 0; return true; } return false; } /*=================================================================================== PrintTestName EFFECTS: Prints information about the current test. If err is false, ends with an ellipsis, because the test is ongoing. If err is true an error is being reported, and the output ends with an endl. ====================================================================================*/ void TestController::PrintTestName(bool err) { if (current_container) EH_STD::cerr<<"["<<current_container<<"] :"; EH_STD::cerr<<"testing "<<current_test <<" (" << current_test_category <<")"; if (err) EH_STD::cerr<<EH_STD::endl; else EH_STD::cerr<<" ... "; } void TestController::ReportSuccess(int count) { if (nc_verbose) EH_STD::cerr<<(count+1)<<" try successful"<<EH_STD::endl; } long& TestController::Failure_threshold() { static long failure_threshold = kNotInExceptionTest; return failure_threshold; }