blob: dd1802ab78d201b50b23a6dcb9880d56ef16887a [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <pthread.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <unittest/unittest.h>
static uint8_t DestructionTrackerStorage[32];
template <bool EnableAdoptionValidator>
class DestructionTracker : public fbl::RefCounted<DestructionTracker<EnableAdoptionValidator>,
EnableAdoptionValidator> {
public:
explicit DestructionTracker(bool* destroyed)
: destroyed_(destroyed) {}
~DestructionTracker() { *destroyed_ = true; }
// During our death tests, we will be doing things which would normally be
// Very Bad for actually heap allocated objects. These tests only ever need
// a single DestructionTracker object to be allocated at a time. Overload
// new/delete so that we are using statically allocated storage and avoid
// doing bad things to our heap.
void* operator new(size_t size) {
ZX_ASSERT(size <= sizeof(DestructionTrackerStorage));
return DestructionTrackerStorage;
}
void* operator new(size_t size, fbl::AllocChecker* ac) {
ZX_ASSERT(ac != nullptr);
ZX_ASSERT(size <= sizeof(DestructionTrackerStorage));
ac->arm(size, true);
return DestructionTrackerStorage;
}
void operator delete(void* ptr) {
ZX_ASSERT(ptr == DestructionTrackerStorage);
}
private:
bool* destroyed_;
};
static_assert(sizeof(DestructionTracker<true>) == sizeof(DestructionTracker<false>),
"DestructionTracker debug vs. release size mismatch!");
static_assert(sizeof(DestructionTracker<true>) <= sizeof(DestructionTrackerStorage),
"Not enough static storage for DestructionTracker<true|false>!");
template <bool EnableAdoptionValidator>
static void* inc_and_dec(void* arg) {
auto tracker = reinterpret_cast<DestructionTracker<EnableAdoptionValidator>*>(arg);
for (size_t i = 0u; i < 500; ++i) {
fbl::RefPtr<DestructionTracker<EnableAdoptionValidator>> ptr(tracker);
}
return nullptr;
}
template <bool EnableAdoptionValidator>
static bool ref_counted_test() {
BEGIN_TEST;
bool destroyed = false;
{
fbl::AllocChecker ac;
fbl::RefPtr<DestructionTracker<EnableAdoptionValidator>> ptr =
fbl::AdoptRef(new (&ac) DestructionTracker<EnableAdoptionValidator>(&destroyed));
EXPECT_TRUE(ac.check());
EXPECT_FALSE(destroyed, "should not be destroyed");
void* arg = reinterpret_cast<void*>(ptr.get());
pthread_t threads[5];
for (size_t i = 0u; i < fbl::count_of(threads); ++i) {
int res = pthread_create(&threads[i], NULL, &inc_and_dec<EnableAdoptionValidator>, arg);
ASSERT_LE(0, res, "Failed to create inc_and_dec thread!");
}
inc_and_dec<EnableAdoptionValidator>(arg);
for (size_t i = 0u; i < fbl::count_of(threads); ++i)
pthread_join(threads[i], NULL);
EXPECT_FALSE(destroyed, "should not be destroyed after inc/dec pairs");
}
EXPECT_TRUE(destroyed, "should be when RefPtr falls out of scope");
END_TEST;
}
template <bool EnableAdoptionValidator>
static bool make_ref_counted_test() {
BEGIN_TEST;
bool destroyed = false;
{
auto ptr = fbl::MakeRefCounted<DestructionTracker<EnableAdoptionValidator>>(&destroyed);
EXPECT_FALSE(destroyed, "should not be destroyed");
}
EXPECT_TRUE(destroyed, "should be when RefPtr falls out of scope");
destroyed = false;
{
fbl::AllocChecker ac;
auto ptr2 = fbl::MakeRefCountedChecked<DestructionTracker<EnableAdoptionValidator>>(
&ac, &destroyed);
EXPECT_TRUE(ac.check());
}
EXPECT_TRUE(destroyed, "should be when RefPtr falls out of scope");
END_TEST;
}
static bool wrap_dead_pointer_asserts() {
BEGIN_TEST;
bool destroyed = false;
DestructionTracker<true>* raw = nullptr;
{
// Create and adopt a ref-counted object, and let it go out of scope.
fbl::AllocChecker ac;
fbl::RefPtr<DestructionTracker<true>> ptr =
fbl::AdoptRef(new (&ac) DestructionTracker<true>(&destroyed));
EXPECT_TRUE(ac.check());
raw = ptr.get();
EXPECT_FALSE(destroyed);
}
EXPECT_TRUE(destroyed);
// Wrapping the now-destroyed object should trigger an assertion.
ASSERT_DEATH(
[](void* void_raw) {
auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
__UNUSED fbl::RefPtr<DestructionTracker<true>> zombie = fbl::WrapRefPtr(raw);
},
raw, "Assert should have fired after wraping dead object\n");
END_TEST;
}
static bool extra_release_asserts() {
BEGIN_TEST;
// Create and adopt a ref-counted object.
bool destroyed = false;
fbl::AllocChecker ac;
DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
ASSERT_TRUE(ac.check());
raw->Adopt();
// Manually release once, which should tell us to delete the object.
EXPECT_TRUE(raw->Release());
// (But it's not deleted since we didn't listen to the return value
// of Release())
EXPECT_FALSE(destroyed);
ASSERT_DEATH(
[](void* void_raw) {
auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
// Manually releasing again should trigger the assertion.
__UNUSED bool unused = raw->Release();
},
raw, "Assert should have fired after releasing object with ref count of zero\n");
// Do not attempt to actually delete the object. It was never actually heap
// allocated, so we are not leaking anything, and the system is in a bad
// state now. Attempting to delete the object can trigger other ASSERTs
// which will crash the test.
END_TEST;
}
static bool wrap_after_last_release_asserts() {
BEGIN_TEST;
// Create and adopt a ref-counted object.
bool destroyed = false;
fbl::AllocChecker ac;
DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
ASSERT_TRUE(ac.check());
raw->Adopt();
// Manually release once, which should tell us to delete the object.
EXPECT_TRUE(raw->Release());
// (But it's not deleted since we didn't listen to the return value
// of Release())
EXPECT_FALSE(destroyed);
ASSERT_DEATH(
[](void* void_raw) {
auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
// Adding another ref (by wrapping) should trigger the assertion.
__UNUSED bool unused = raw->Release();
},
raw, "Assert should have fired after wraping object with ref count of zero\n");
// Do not attempt to actually delete the object. See previous comments.
END_TEST;
}
static bool unadopted_add_ref_asserts() {
BEGIN_TEST;
// Create an un-adopted ref-counted object.
bool destroyed = false;
fbl::AllocChecker ac;
DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
ASSERT_TRUE(ac.check());
ASSERT_DEATH(
[](void* void_raw) {
auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
// Adding a ref (by wrapping) without adopting first should trigger an
// assertion.
fbl::RefPtr<DestructionTracker<true>> unadopted = fbl::WrapRefPtr(raw);
},
raw, "Assert should have fired after wraping non-adopted object\n");
// Do not attempt to actually delete the object. See previous comments.
END_TEST;
}
static bool unadopted_release_asserts() {
BEGIN_TEST;
// Create an un-adopted ref-counted object.
bool destroyed = false;
fbl::AllocChecker ac;
DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
ASSERT_TRUE(ac.check());
ASSERT_DEATH(
[](void* void_raw) {
auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
// Releasing without adopting first should trigger an assertion.
__UNUSED bool unused = raw->Release();
},
raw, "Assert should have fired after releasing non-adopted object\n");
// Do not attempt to actually delete the object. See previous comments.
END_TEST;
}
BEGIN_TEST_CASE(ref_counted_tests)
RUN_NAMED_TEST("Ref Counted (adoption validation on)", ref_counted_test<true>)
RUN_NAMED_TEST("Ref Counted (adoption validation off)", ref_counted_test<false>)
RUN_NAMED_TEST("Make Ref Counted (adoption validation on)", make_ref_counted_test<true>)
RUN_NAMED_TEST("Make Ref Counted (adoption validation off)", make_ref_counted_test<false>)
RUN_NAMED_TEST("Wrapping dead pointer should assert", wrap_dead_pointer_asserts)
RUN_NAMED_TEST("Extra release should assert", extra_release_asserts)
RUN_NAMED_TEST("Wrapping zero-count pointer should assert", wrap_after_last_release_asserts)
RUN_NAMED_TEST("AddRef on unadopted object should assert", unadopted_add_ref_asserts)
RUN_NAMED_TEST("Release on unadopted object should assert", unadopted_release_asserts)
END_TEST_CASE(ref_counted_tests);