/* * Copyright (c) 1994 by Xerox Corporation. All rights reserved. * * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. * * Permission is hereby granted to use or copy this program * for any purpose, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was * modified is included with the above copyright notice. */ // This program tries to test the specific C++ functionality provided by // gc_cpp.h that isn't tested by the more general test routines of the // collector. #ifdef HAVE_CONFIG_H # include "config.h" #endif #undef GC_BUILD #define GC_DONT_INCL_WINDOWS_H #include "gc_cpp.h" #include #include #define GC_NAMESPACE_ALLOCATOR #include "gc/gc_allocator.h" using boehmgc::gc_allocator; using boehmgc::gc_allocator_ignore_off_page; using boehmgc::traceable_allocator; # include "private/gcconfig.h" # ifndef GC_API_PRIV # define GC_API_PRIV GC_API # endif extern "C" { GC_API_PRIV void GC_printf(const char * format, ...); /* Use GC private output to reach the same log file. */ /* Don't include gc_priv.h, since that may include Windows system */ /* header files that don't take kindly to this context. */ } #ifdef MSWIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN 1 # endif # define NOSERVICE # include #endif #ifdef GC_NAME_CONFLICT # define USE_GC GC_NS_QUALIFY(UseGC) struct foo * GC; #else # define USE_GC GC_NS_QUALIFY(GC) #endif #define my_assert( e ) \ if (! (e)) { \ GC_printf( "Assertion failure in " __FILE__ ", line %d: " #e "\n", \ __LINE__ ); \ exit( 1 ); } #if defined(__powerpc64__) && !defined(__clang__) && GC_GNUC_PREREQ(10, 0) /* Suppress "layout of aggregates ... has changed" GCC note. */ # define A_I_TYPE short #else # define A_I_TYPE int #endif class A {public: /* An uncollectible class. */ GC_ATTR_EXPLICIT A(int iArg): i(static_cast(iArg)) {} void Test( int iArg ) { my_assert( i == iArg );} virtual ~A() {} A_I_TYPE i; }; class B: public GC_NS_QUALIFY(gc), public A { public: /* A collectible class. */ GC_ATTR_EXPLICIT B( int j ): A( j ) {} virtual ~B() { my_assert( deleting );} static void Deleting( int on ) { deleting = on;} static int deleting;}; int B::deleting = 0; #define C_INIT_LEFT_RIGHT(arg_l, arg_r) \ { \ C *l = new C(arg_l); \ C *r = new C(arg_r); \ left = l; \ right = r; \ if (GC_is_heap_ptr(this)) { \ GC_END_STUBBORN_CHANGE(this); \ GC_reachable_here(l); \ GC_reachable_here(r); \ } \ } class C: public GC_NS_QUALIFY(gc_cleanup), public A { public: /* A collectible class with cleanup and virtual multiple inheritance. */ // The class uses dynamic memory/resource allocation, so provide both // a copy constructor and an assignment operator to workaround a cppcheck // warning. C(const C& c) : A(c.i), level(c.level), left(0), right(0) { if (level > 0) C_INIT_LEFT_RIGHT(*c.left, *c.right); } C& operator=(const C& c) { if (this != &c) { delete left; delete right; i = c.i; level = c.level; left = 0; right = 0; if (level > 0) C_INIT_LEFT_RIGHT(*c.left, *c.right); } return *this; } GC_ATTR_EXPLICIT C( int levelArg ): A( levelArg ), level( levelArg ) { nAllocated++; if (level > 0) { C_INIT_LEFT_RIGHT(level - 1, level - 1); } else { left = right = 0;}} ~C() { this->A::Test( level ); nFreed++; my_assert( level == 0 ? left == 0 && right == 0 : level == left->level + 1 && level == right->level + 1 ); left = right = 0; level = -123456;} static void Test() { if (GC_is_incremental_mode() && nFreed < (nAllocated / 5) * 4) { // An explicit GC might be needed to reach the expected number // of the finalized objects. GC_gcollect(); } my_assert(nFreed <= nAllocated); # ifndef GC_NO_FINALIZATION my_assert(nFreed >= (nAllocated / 5) * 4 || GC_get_find_leak()); # endif } static int nFreed; static int nAllocated; int level; C* left; C* right;}; int C::nFreed = 0; int C::nAllocated = 0; class D: public GC_NS_QUALIFY(gc) { public: /* A collectible class with a static member function to be used as an explicit clean-up function supplied to ::new. */ GC_ATTR_EXPLICIT D( int iArg ): i( iArg ) { nAllocated++;} static void CleanUp( void* obj, void* data ) { D* self = static_cast(obj); nFreed++; my_assert(static_cast(self->i) == reinterpret_cast(data)); } static void Test() { # ifndef GC_NO_FINALIZATION my_assert(nFreed >= (nAllocated / 5) * 4 || GC_get_find_leak()); # endif } int i; static int nFreed; static int nAllocated;}; int D::nFreed = 0; int D::nAllocated = 0; class E: public GC_NS_QUALIFY(gc_cleanup) { public: /* A collectible class with clean-up for use by F. */ E() { nAllocated++;} ~E() { nFreed++;} static int nFreed; static int nAllocated;}; int E::nFreed = 0; int E::nAllocated = 0; class F: public E {public: /* A collectible class with clean-up, a base with clean-up, and a member with clean-up. */ F() { nAllocatedF++; } ~F() { nFreedF++; } static void Test() { # ifndef GC_NO_FINALIZATION my_assert(nFreedF >= (nAllocatedF / 5) * 4 || GC_get_find_leak()); # endif my_assert(2 * nFreedF == nFreed); } E e; static int nFreedF; static int nAllocatedF; }; int F::nFreedF = 0; int F::nAllocatedF = 0; GC_word Disguise( void* p ) { return GC_HIDE_POINTER(p); } void* Undisguise( GC_word i ) { return GC_REVEAL_POINTER(i); } #define GC_CHECKED_DELETE(p) \ { \ size_t freed_before = GC_get_expl_freed_bytes_since_gc(); \ delete p; /* the operator should invoke GC_FREE() */ \ size_t freed_after = GC_get_expl_freed_bytes_since_gc(); \ my_assert(freed_before != freed_after); \ } #define N_TESTS 7 #if ((defined(MSWIN32) && !defined(__MINGW32__)) || defined(MSWINCE)) \ && !defined(NO_WINMAIN_ENTRY) int APIENTRY WinMain( HINSTANCE /* instance */, HINSTANCE /* prev */, LPSTR cmd, int /* cmdShow */) { int argc = 0; char* argv[ 3 ]; # if defined(CPPCHECK) GC_noop1(reinterpret_cast(&WinMain)); # endif if (cmd != 0) for (argc = 1; argc < static_cast(sizeof(argv) / sizeof(argv[0])); argc++) { // Parse the command-line string. Non-reentrant strtok() is not used // to avoid complains of static analysis tools. (And, strtok_r() is // not available on some platforms.) The code is equivalent to: // if (!(argv[argc] = strtok(argc == 1 ? cmd : 0, " \t"))) break; if (NULL == cmd) { argv[argc] = NULL; break; } for (; *cmd != '\0'; cmd++) { if (*cmd != ' ' && *cmd != '\t') break; } if ('\0' == *cmd) { argv[argc] = NULL; break; } argv[argc] = cmd; while (*(++cmd) != '\0') { if (*cmd == ' ' || *cmd == '\t') break; } if (*cmd != '\0') { *(cmd++) = '\0'; } else { cmd = NULL; } } #elif defined(MACOS) int main() { char* argv_[] = {"cpptest", "7"}; // MacOS doesn't have a command line argv = argv_; argc = sizeof(argv_) / sizeof(argv_[0]); #else int main( int argc, char* argv[] ) { #endif GC_set_all_interior_pointers(1); /* needed due to C++ multiple inheritance used */ # ifdef TEST_MANUAL_VDB GC_set_manual_vdb_allowed(1); # endif GC_INIT(); # ifndef NO_INCREMENTAL GC_enable_incremental(); # endif if (GC_get_find_leak()) GC_printf("This test program is not designed for leak detection mode\n"); int i, iters, n; int *x = gc_allocator().allocate(1); int *xio; xio = gc_allocator_ignore_off_page().allocate(1); GC_reachable_here(xio); int **xptr = traceable_allocator().allocate(1); *x = 29; if (!xptr) { GC_printf("Out of memory!\n"); exit(3); } GC_PTR_STORE_AND_DIRTY(xptr, x); x = 0; if (argc != 2 || (n = atoi(argv[1])) <= 0) { GC_printf("usage: cpptest \n" "Assuming %d iterations\n", N_TESTS); n = N_TESTS; } # ifdef LINT2 if (n > 100 * 1000) n = 100 * 1000; # endif for (iters = 1; iters <= n; iters++) { GC_printf( "Starting iteration %d\n", iters ); /* Allocate some uncollectible As and disguise their pointers. Later we'll check to see if the objects are still there. We're checking to make sure these objects really are uncollectible. */ GC_word as[ 1000 ]; GC_word bs[ 1000 ]; for (i = 0; i < 1000; i++) { as[ i ] = Disguise( new (GC_NS_QUALIFY(NoGC)) A(i) ); bs[ i ] = Disguise( new (GC_NS_QUALIFY(NoGC)) B(i) ); } /* Allocate a fair number of finalizable Cs, Ds, and Fs. Later we'll check to make sure they've gone away. */ for (i = 0; i < 1000; i++) { C* c = new C( 2 ); C c1( 2 ); /* stack allocation should work too */ D* d; F* f; d = ::new (USE_GC, D::CleanUp, reinterpret_cast(static_cast(i))) D(i); GC_reachable_here(d); f = new F; F** fa = new F*[1]; fa[0] = f; (void)fa; delete[] fa; if (0 == i % 10) GC_CHECKED_DELETE(c); } /* Allocate a very large number of collectible As and Bs and drop the references to them immediately, forcing many collections. */ for (i = 0; i < 1000000; i++) { A* a; a = new (USE_GC) A( i ); GC_reachable_here(a); B* b; b = new B( i ); (void)b; b = new (USE_GC) B( i ); if (0 == i % 10) { B::Deleting( 1 ); GC_CHECKED_DELETE(b); B::Deleting( 0 );} # if defined(FINALIZE_ON_DEMAND) && !defined(GC_NO_FINALIZATION) GC_invoke_finalizers(); # endif } /* Make sure the uncollectible As and Bs are still there. */ for (i = 0; i < 1000; i++) { A* a = static_cast(Undisguise(as[i])); B* b = static_cast(Undisguise(bs[i])); a->Test( i ); # if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) // Workaround for ASan/MSan: the linker uses operator delete // implementation from libclang_rt instead of gc_cpp (thus // causing incompatible alloc/free). GC_FREE(a); # else GC_CHECKED_DELETE(a); # endif b->Test( i ); B::Deleting( 1 ); GC_CHECKED_DELETE(b); B::Deleting( 0 ); # if defined(FINALIZE_ON_DEMAND) && !defined(GC_NO_FINALIZATION) GC_invoke_finalizers(); # endif } /* Make sure most of the finalizable Cs, Ds, and Fs have gone away. */ C::Test(); D::Test(); F::Test();} x = *xptr; my_assert(29 == x[0]); GC_printf("The test appears to have succeeded.\n"); return 0; }