blob: 43c18cca0d1f2af87dfe56b920d2368321ad5735 [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.
#ifndef FBL_REF_COUNTED_INTERNAL_H_
#define FBL_REF_COUNTED_INTERNAL_H_
#include <atomic>
#include <zircon/assert.h>
#include <zircon/compiler.h>
namespace fbl {
namespace internal {
// Adoption validation will help to catch:
// - Double-adoptions
// - AddRef/Release without adopting first
// - Re-wrapping raw pointers to destroyed objects
//
// It also provides some limited defense against
// - Wrapping bad pointers
template <bool EnableAdoptionValidator>
class RefCountedBase {
protected:
constexpr RefCountedBase()
: ref_count_(kPreAdoptSentinel) {}
~RefCountedBase() {
if (EnableAdoptionValidator) {
// Reset the ref-count back to the pre-adopt sentinel value so that we
// have the best chance of catching a use-after-free situation, even if
// we have a messed up mix of debug/release translation units being
// linked together.
ref_count_.store(kPreAdoptSentinel, std::memory_order_release);
}
}
void AddRef() const {
const int32_t rc = ref_count_.fetch_add(1, std::memory_order_relaxed);
// This assertion will fire if either of the following occur.
//
// 1) someone calls AddRef() before the object has been properly
// Adopted.
//
// 2) someone calls AddRef() on a ref-counted object that has
// reached ref_count_ == 0 but has not been destroyed yet. This
// could happen by manually calling AddRef(), or re-wrapping such a
// pointer with WrapRefPtr() or RefPtr<T>(T*) (both of which call
// AddRef()).
//
// Note: leave the ASSERT on in all builds. The constant
// EnableAdoptionValidator check above should cause this code path to be
// pruned in release builds, but leaving this as an always on ASSERT
// will mean that the tests continue to function even when built as
// release.
if (EnableAdoptionValidator) {
ZX_ASSERT_MSG(rc >= 1, "count %d(0x%08x) < 1\n", rc, static_cast<uint32_t>(rc));
}
}
// Returns true if the object should self-delete.
bool Release() const __WARN_UNUSED_RESULT {
const int32_t rc = ref_count_.fetch_sub(1, std::memory_order_release);
// This assertion will fire if someone manually calls Release()
// on a ref-counted object too many times, or if Release is called
// before an object has been Adopted.
//
// Note: leave the ASSERT on in all builds. The constant
// EnableAdoptionValidator check above should cause this code path to be
// pruned in release builds, but leaving this as an always on ASSERT
// will mean that the tests continue to function even when built as
// release.
if (EnableAdoptionValidator) {
ZX_ASSERT_MSG(rc >= 1, "count %d(0x%08x) < 1\n", rc, static_cast<uint32_t>(rc));
}
if (rc == 1) {
atomic_thread_fence(std::memory_order_acquire);
return true;
}
return false;
}
void Adopt() const {
// TODO(johngro): turn this into an if-constexpr when we have moved up
// to C++17
if (EnableAdoptionValidator) {
int32_t expected = kPreAdoptSentinel;
bool res = ref_count_.compare_exchange_strong(expected, 1,
std::memory_order_acq_rel,
std::memory_order_acquire);
// Note: leave the ASSERT on in all builds. The constant
// EnableAdoptionValidator check above should cause this code path
// to be pruned in release builds, but leaving this as an always on
// ASSERT will mean that the tests continue to function even when
// built as release.
ZX_ASSERT_MSG(res,
"count(0x%08x) != sentinel(0x%08x)\n",
static_cast<uint32_t>(expected),
static_cast<uint32_t>(kPreAdoptSentinel));
} else {
ref_count_.store(1, std::memory_order_release);
}
}
// Current ref count. Only to be used for debugging purposes.
int ref_count_debug() const {
return ref_count_.load(std::memory_order_relaxed);
}
// Note:
//
// The PreAdoptSentinel value is chosen specifically to be negative when
// stored as an int32_t, and as far away from becoming positive (via either
// addition or subtraction) as possible. These properties allow us to
// combine the debug-build adopt sanity checks and the lifecycle sanity
// checks into a single debug assert.
//
// If a user creates an object, but never adopts it, they would need to
// perform 0x4000000 (about 1 billion) unchecked AddRef or Release
// operations before making the internal ref_count become positive again.
// At this point, even a checked AddRef or Release operation would fail to
// detect the bad state of the system fails to detect the problem.
//
static constexpr int32_t kPreAdoptSentinel = static_cast<int32_t>(0xC0000000);
mutable std::atomic_int32_t ref_count_;
};
} // namespace internal
} // namespace fbl
#endif // FBL_REF_COUNTED_INTERNAL_H_