| // 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_ |