/* * Usage: -t secs * where secs is how long to run the thread test for. Defaults * to 15 seconds. */ // NOTE compile with -pthread #include "SharedPtr.hpp" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace cs440; class Random { public: Random(unsigned int s = 0); // Generate random int in range [l,h]. int operator()(int l, int h) { return std::uniform_int_distribution(l, h)(gen); } Random(const Random &) = delete; Random &operator=(const Random &) = delete; private: std::default_random_engine gen; }; Random::Random(unsigned int seed) : gen(seed) {} void basic_tests_1(); void basic_tests_2(); // RunSecs needs to be here so that it can be set via command-line arg. int RunSecs = 15; void threaded_test(); size_t AllocatedSpace; void usage() { fprintf(stderr, "Bad args, usage: ./a.out [ -t secs ]\n"); exit(1); } int main(int argc, char *argv[]) { int c; setlinebuf(stdout); { // Force initial iostreams allocation so that memory-leak detecting // will work right. Tabs and pointer are to get locale stuff // allocated. int *p = (int *)0x1234; cout << "\tForce initial allocation on cout: " << p << endl; cerr << "\tForce initial allocation on cerr: " << p << endl; clog << "\tForce initial allocation on clog: " << p << endl; } while ((c = getopt(argc, argv, "t:")) != -1) { switch (c) { case 't': RunSecs = atoi(optarg); assert(1 <= RunSecs && RunSecs <= 10000); break; case '?': usage(); break; default: assert(false); abort(); } } // Catch any command-line args without -. if (optind < argc) { usage(); } // Initial printf(), to force any internal allocation. printf("Tests below:\n"); basic_tests_1(); basic_tests_2(); threaded_test(); } void *operator new(size_t sz) { char *p = (char *)malloc(sz + 8); *((size_t *)p) = sz; __sync_add_and_fetch(&AllocatedSpace, sz); return p + 8; } void operator delete(void *vp) noexcept { if (vp == 0) { return; } char *p = (char *)vp; size_t sz = *((size_t *)(p - 8)); __sync_sub_and_fetch(&AllocatedSpace, sz); // Zero out memory to help catch bugs. memset(p - 8, 0xff, sz + 8); free(p - 8); } /* Basic Tests 1 * ================================================================================ */ struct Base1 { Base1() : derived_destructor_called(false) { printf("Base1::Base1()\n"); } Base1(const Base1 &); // Disallow. Base1 &operator=(const Base1 &); // Disallow. ~Base1() { printf("Base1::~Base1()\n"); assert(derived_destructor_called); } protected: bool derived_destructor_called; }; class Derived : public Base1 { friend void basic_tests_1(); private: Derived() {} Derived(const Derived &); // Disallow. Derived &operator=(const Derived &); // Disallow. public: ~Derived() { printf("Derived::~Derived()\n"); derived_destructor_called = true; } int value; }; struct Base_polymorphic { Base_polymorphic() { printf("Base_polymorphic::Base_polymorphic()\n"); } Base_polymorphic(const Base_polymorphic &); // Disallow. Base_polymorphic &operator=(const Base_polymorphic &); // Disallow. virtual ~Base_polymorphic() { printf("Base_polymorphic::~Base_polymorphic()\n"); } }; class Derived_polymorphic : public Base_polymorphic { friend void basic_tests_1(); private: Derived_polymorphic() {} Derived_polymorphic(const Derived_polymorphic &); // Disallow. Derived_polymorphic &operator=(const Derived_polymorphic &); // Disallow. }; class Derived2_polymorphic : public Base_polymorphic { private: Derived2_polymorphic() {} Derived2_polymorphic(const Derived2_polymorphic &); // Disallow. Derived2_polymorphic &operator=(const Derived2_polymorphic &); // Disallow. }; struct Base2 { Base2() : derived_destructor_called(false) { printf("Base2::Base2()\n"); } Base2(const Base2 &); // Disallow. Base2 &operator=(const Base2 &); // Disallow. ~Base2() { printf("Base2::~Base2()\n"); assert(derived_destructor_called); } bool derived_destructor_called; }; class Derived_mi : public Base1, public Base2 { friend void basic_tests_1(); private: Derived_mi() {} Derived_mi(const Derived_mi &); // Disallow. Derived_mi &operator=(const Derived_mi &); // Disallow. public: ~Derived_mi() { printf("Derived_mi::~Derived_mi()\n"); Base1::derived_destructor_called = true; Base2::derived_destructor_called = true; } int value; }; struct Base1_vi { Base1_vi() : derived_destructor_called(false) { printf("Base1_vi::Base1_vi()\n"); } Base1_vi(const Base1_vi &); // Disallow. Base1_vi &operator=(const Base1_vi &); // Disallow. ~Base1_vi() { printf("Base1_vi::~Base1_vi()\n"); assert(derived_destructor_called); } bool derived_destructor_called; }; struct Base2_vi : public virtual Base1_vi { Base2_vi() { printf("Base2_vi::Base2_vi()\n"); } Base2_vi(const Base2_vi &); // Disallow. Base2_vi &operator=(const Base2_vi &); // Disallow. ~Base2_vi() { printf("Base2_vi::~Base2_vi()\n"); assert(derived_destructor_called); } }; class Derived_vi : public virtual Base1_vi, public Base2_vi { friend void basic_tests_1(); private: Derived_vi() {} Derived_vi(const Derived_vi &); // Disallow. Derived_vi &operator=(const Derived_vi &); // Disallow. public: ~Derived_vi() { printf("Derived_vi::~Derived_vi()\n"); derived_destructor_called = true; } int value; }; void basic_tests_1() { size_t base = AllocatedSpace; // Test deleting through original class. { // Base1 created directly with Derived *. { SharedPtr sp(new Derived); { // Test copy constructor. SharedPtr sp2(sp); } } // Base1 assigned from SharedPtr. { SharedPtr sp2; { SharedPtr sp(new Derived); // Test template copy constructor. // SharedPtr sp3(sp); // sp2 = sp; // sp2 = sp2; } } } // Test copy constructor and template copy constructor. { { SharedPtr sp(new Derived); SharedPtr sp2(sp); } { SharedPtr sp(new Derived); // SharedPtr sp2(sp); } } // Test assignment operator and template assignment operator. { { SharedPtr sp(new Derived); SharedPtr sp2; sp2 = sp; sp = sp; // Self assignment. } { SharedPtr sp(new Derived); SharedPtr sp2; // sp2 = sp; sp2 = sp2; // Self assignment. // sp2 = sp; sp = sp; } } // Test reset. { { SharedPtr sp(new Derived); SharedPtr sp2; // sp2 = sp; sp2 = sp2; sp.reset(); sp.reset(new Derived); SharedPtr sp3(sp2); } // Need to make sure that it's the reset that // is forcing the object to be deleted, and // not the smart pointer destructor. // Commented out for now, since I think this // test causes more trouble than it's worth. // Need to rewrite it so that I can test instead // by monitoring side-effect from a test class // dtor. /* { SharedPtr sp(new Derived); char *buf = (char *) ::operator new(sizeof(SharedPtr)); SharedPtr &sp2 = *(new (buf) SharedPtr()); sp2 = sp; sp2 = sp2; sp.reset(); sp2.reset(); ::operator delete(buf); } */ } // Test *. { SharedPtr sp(new Derived); (*sp).value = 1234; // SharedPtr sp2(sp); // int i = (*sp2).value; // assert(i == 1234); // (*sp2).value = 567; // Should give a syntax error if uncommented. } // Test ->. { SharedPtr sp(new Derived); sp->value = 1234; // SharedPtr sp2(sp); // int i = sp2->value; // assert(i == 1234); // sp2->value = 567; // Should give a syntax error if uncommented. } // Test get(). { SharedPtr sp(new Derived); sp.get()->value = 1234; // SharedPtr sp2(sp); // int i = sp2.get()->value; // assert(i == 1234); } // Test bool. { SharedPtr sp(new Derived); assert(sp); sp.reset(); assert(!sp); SharedPtr sp2; // int i = sp; // Must give syntax error. // delete sp; // Must give syntax error. } // Test ==. { SharedPtr sp(new Derived); SharedPtr sp2(sp); SharedPtr sp3; assert(sp2 == sp); assert(!(sp2 == sp3)); sp3.reset(new Derived); assert(!(sp2 == sp3)); } // Test static_pointer_cast. { SharedPtr sp(new Derived); // SharedPtr sp2(sp); // SharedPtr sp3(sp2); // Should give a syntax error. // SharedPtr sp3(static_pointer_cast(sp2)); // SharedPtr sp4(dynamic_pointer_cast(sp2)); // Should // give syntax error about polymorphism. } // Test dynamic_pointer_cast. { SharedPtr sp(new Derived_polymorphic); // SharedPtr sp2(sp); // SharedPtr sp3(sp2); // Should give a syntax error. // SharedPtr sp3( // dynamic_pointer_cast(sp2)); // SharedPtr sp4( // static_pointer_cast(sp2)); // SharedPtr sp5( // dynamic_pointer_cast(sp2)); // assert(!sp5); } // Test to make sure works with MI. { SharedPtr sp2; { SharedPtr sp1; { // SharedPtr sp(new Derived_mi); // sp1 = sp; // sp2 = // static_pointer_cast(static_pointer_cast(sp1)); } // Destructor for sp called. } // Destructor for sp1 called. } // Destructor for sp2 called. // Test with MI to make sure not doing invalid casts elsewhere. { { SharedPtr sp1; { SharedPtr sp(new Derived_mi); // sp1 = // static_pointer_cast(static_pointer_cast(sp)); } // Destructor for sp called. } // Destructor for sp1 called. { SharedPtr sp2; { SharedPtr sp(new Derived_mi); // sp2 = // static_pointer_cast(static_pointer_cast(sp)); } // Destructor for sp called. } // Destructor for sp2 called. } // Should add test here to make sure not doing cast like from Helper * to // Helper *. // Test to make sure works with virtual inheritance. { SharedPtr sp2; { SharedPtr sp1; { SharedPtr sp(new Derived_vi); // sp1 = sp; // sp2 = sp; } // Destructor for sp called. } // Destructor for sp1 called. } // Destructor for sp2 called. // Test const assignment. { SharedPtr sp_const(new Derived); SharedPtr sp(new Derived); // sp_const = sp; // sp = sp_const; // Syntax error if uncommented. } if (base != AllocatedSpace) { printf("Leaked %zu bytes in basic tests 1.\n", AllocatedSpace - base); // abort(); } printf("Basic tests 1 passed.\n"); } /* Basic Tests 2 * ================================================================================ */ class A { public: virtual ~A() { // printf("%p deleted\n", (Obj *) this); } }; class B : public A { public: virtual ~B() {} }; class C : public A { public: virtual ~C() {} }; // These tests overlap a lot with the ones in basic tests 1. void basic_tests_2() { size_t base = AllocatedSpace; { // Test default constructor. { SharedPtr np; assert(!np); } // Test construction from object. { A *ap = new A; SharedPtr a(ap); assert(a.get() == ap); } // Test construction from another SharedPtr of the same type. { SharedPtr a(new A); SharedPtr a2(a); } // Test construction from another SharedPtr of a derived type. { SharedPtr b(new B); // SharedPtr a(b); } // Test assignment operator. { // Same type. SharedPtr a1, a2(new A); a1 = a2; // Derived to base. SharedPtr b(new B); // a1 = b; // Object ptr. a1.reset(new A); // To Null. a1.reset(); } // More misc tests. { SharedPtr b(new B); SharedPtr c(new C); /* printf("new B: %p\n", static_cast(b.ptr())); printf("new C: %p\n", static_cast(c.ptr())); printf("b: %p\n", &b); printf("c: %p\n", &c); */ // Initialization from base should not require cast. // SharedPtr a_base(b); // Default initialization and cast to base. // SharedPtr a_dc(b); // Note that this will use the templated constructor to do a conversion. // if a templated assignment does not exist. // a_dc = b; // Static cast to base. // SharedPtr a_b = b; // SharedPtr a_c = c; /* printf("a_b: %p\n", &a_b); printf("a_c: %p\n", &a_c); */ // Dynamic downcast. // SharedPtr b2 = dynamic_pointer_cast(a_b); // If the below is uncommented, we should get an error in the templated // assignment operator. This will verify that that is being used rather // than templated constructors, etc. // b2 = a_b; // assert(b2); // SharedPtr c2 = dynamic_pointer_cast(a_b); // assert(!c2); /* printf("b2: %p\n", &b2); printf("c2: %p\n", &c2); */ // Dynamic downcast. // SharedPtr b3 = dynamic_pointer_cast(a_c); // assert(!b3); // SharedPtr c3 = dynamic_pointer_cast(a_c); // assert(c3); /* printf("b3: %p\n", &b3); printf("c3: %p\n", &c3); */ } } if (base != AllocatedSpace) { printf("Leaked %zu bytes in basic tests 2.\n", AllocatedSpace - base); // abort(); } printf("Basic tests 2 passed.\n"); } /* Threaded Test * * ============================================================================== */ // These need to be global so the threads can access it. time_t StartTime; const int TABLE_SIZE = 100; class TestObj { public: TestObj() {} TestObj(int i) : a(i) {} int a; }; struct TableEntry { TableEntry(); ~TableEntry(); pthread_mutex_t lock; SharedPtr *ptr; } *Table; TableEntry::TableEntry() : ptr(0) { int ec; ec = pthread_mutex_init(&lock, 0); assert(ec == 0); } TableEntry::~TableEntry() { int ec; ec = pthread_mutex_destroy(&lock); assert(ec == 0); if (ptr != 0) { delete ptr; ptr = 0; } } void *run(void *vp) { Random rand((unsigned int)(unsigned long)vp); pthread_t tid = pthread_self(); int ec; struct Counters { unsigned long assignment_new, assignment, reset, new_default, new_copy, delet; } counters = {0, 0, 0, 0, 0, 0}; while (time(NULL) < StartTime + RunSecs) { int act = rand(0, 4); switch (act) { case 0: // Assign ptr new TestObj; { int i = rand(0, TABLE_SIZE - 1); // printf("%d: new %d start\n", (int)tid, i); ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); if (Table[i].ptr != 0) { Table[i].ptr->reset(new TestObj); // fix counters.assignment_new++; } ec = pthread_mutex_unlock(&Table[i].lock); assert(ec == 0); // printf("%d: new %d done\n", (int) tid, i); } break; case 1: // Set ptr to NULL { int i = rand(0, TABLE_SIZE - 1); // printf("%d: clear %d start\n", (int) tid, i); ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); if (Table[i].ptr != 0) { Table[i].ptr->reset(); counters.reset++; } ec = pthread_mutex_unlock(&Table[i].lock); assert(ec == 0); // printf("%d: clear %d done\n", (int) tid, i); } break; case 2: // Assign ptr from another ptr { int i = rand(0, TABLE_SIZE - 1); int j = rand(0, TABLE_SIZE - 1); // printf("%d: assign %d=%d start\n", (int) tid, i, j); // Order to avoid deadlock. if (i <= j) { ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); if (i != j) { ec = pthread_mutex_lock(&Table[j].lock); assert(ec == 0); } } else { ec = pthread_mutex_lock(&Table[j].lock); assert(ec == 0); ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); } if (Table[i].ptr && Table[j].ptr) { *Table[i].ptr = *Table[j].ptr; counters.assignment++; } ec = pthread_mutex_unlock(&Table[i].lock); assert(ec == 0); if (i != j) { ec = pthread_mutex_unlock(&Table[j].lock); assert(ec == 0); } // printf("%d: assign %d=%d done\n", (int) tid, i, j); } break; case 3: // Create new sptr. { // Create with default. if (rand(0, 1) == 0) { int i = rand(0, TABLE_SIZE - 1); ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); if (!Table[i].ptr) { Table[i].ptr = new SharedPtr; counters.new_default++; } ec = pthread_mutex_unlock(&Table[i].lock); assert(ec == 0); // Create with copy constructor. } else { int i = rand(0, TABLE_SIZE - 1), j; do { j = rand(0, TABLE_SIZE - 1); } while (i == j); // Order to avoid deadlock. if (i < j) { ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); ec = pthread_mutex_lock(&Table[j].lock); assert(ec == 0); } else { ec = pthread_mutex_lock(&Table[j].lock); assert(ec == 0); ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); } if (!Table[i].ptr && Table[j].ptr) { Table[i].ptr = new SharedPtr(*Table[j].ptr); counters.new_copy++; } ec = pthread_mutex_unlock(&Table[i].lock); assert(ec == 0); ec = pthread_mutex_unlock(&Table[j].lock); assert(ec == 0); // printf("%d: assign %d=%d done\n", (int) tid, i, j); } } break; case 4: // Delete { int i = rand(0, TABLE_SIZE - 1); ec = pthread_mutex_lock(&Table[i].lock); assert(ec == 0); if (Table[i].ptr) { delete Table[i].ptr; Table[i].ptr = 0; counters.delet++; } ec = pthread_mutex_unlock(&Table[i].lock); assert(ec == 0); } break; default: fprintf(stderr, "Bad case: %d\n", act); abort(); break; } } double rs = RunSecs; printf("%10lu: assignment_new=%lu ops, assignment=%lu ops, reset=%lu ops, " "new_default=%lu ops, new_copy=%lu ops, delete=%lu ops\n" " rates: assignment_new=%.0f ops/sec, assignment=%.0f ops/sec, " "reset=%.0f ops/sec, new_default=%.0f ops/sec, new_copy=%.0f ops/sec, " "delete=%.0f ops/sec\n", (unsigned long)tid, counters.assignment_new, counters.assignment, counters.reset, counters.new_default, counters.new_copy, counters.delet, double(counters.assignment_new) / rs, double(counters.assignment) / rs, double(counters.reset) / rs, double(counters.new_default) / rs, double(counters.new_copy) / rs, double(counters.delet) / rs); return NULL; } void threaded_test() { int ec; size_t base = AllocatedSpace; Table = new TableEntry[TABLE_SIZE]; printf("Running threaded test for %d seconds.\n", RunSecs); StartTime = time(NULL); pthread_t tid1, tid2, tid3, tid4; ec = pthread_create(&tid1, 0, run, (void *)1); assert(ec == 0); ec = pthread_create(&tid2, 0, run, (void *)2); assert(ec == 0); ec = pthread_create(&tid3, 0, run, (void *)3); assert(ec == 0); ec = pthread_create(&tid4, 0, run, (void *)3); assert(ec == 0); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_join(tid4, NULL); delete[] Table; if (base != AllocatedSpace) { printf("Leaked %zu bytes in threaded test.\n", AllocatedSpace - base); abort(); } } /* Local Variables: */ /* c-basic-offset: 4 */ /* End: */ /* vim: set filetype=cpp tabstop=8 shiftwidth=4 softtabstop=4: */