/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Diagnostic class template that helps finding dangling pointers. #ifndef mozilla_CheckedUnsafePtr_h #define mozilla_CheckedUnsafePtr_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/DataMutex.h" #include "nsTArray.h" #include #include #include namespace mozilla { enum class CheckingSupport { Disabled, Enabled, }; template class CheckedUnsafePtr; namespace detail { class CheckedUnsafePtrBaseCheckingEnabled; struct CheckedUnsafePtrCheckData { using Data = nsTArray; DataMutex mPtrs{"mozilla::SupportsCheckedUnsafePtr"}; }; class CheckedUnsafePtrBaseCheckingEnabled { friend class CheckedUnsafePtrBaseAccess; protected: constexpr CheckedUnsafePtrBaseCheckingEnabled() = default; CheckedUnsafePtrBaseCheckingEnabled( const CheckedUnsafePtrBaseCheckingEnabled& aOther) = default; // When copying an CheckedUnsafePtr, its mIsDangling member must be copied as // well; otherwise the new copy might try to dereference a dangling pointer // when destructed. void CopyDanglingFlagIfAvailableFrom( const CheckedUnsafePtrBaseCheckingEnabled& aOther) { mIsDangling = aOther.mIsDangling; } template using DisableForCheckedUnsafePtr = std::enable_if_t< !std::is_base_of::value>; // When constructing an CheckedUnsafePtr from a different kind of pointer it's // not possible to determine whether it's dangling; therefore it's undefined // behavior to construct one from a dangling pointer, and we assume that any // CheckedUnsafePtr thus constructed is not dangling. template DisableForCheckedUnsafePtr CopyDanglingFlagIfAvailableFrom(const Ptr&) {} template void WithCheckedUnsafePtrsImpl(CheckedUnsafePtrCheckData* const aRawPtr, F&& aClosure) { if (!mIsDangling && aRawPtr) { const auto CheckedUnsafePtrs = aRawPtr->mPtrs.Lock(); aClosure(this, *CheckedUnsafePtrs); } } private: bool mIsDangling = false; }; class CheckedUnsafePtrBaseAccess { protected: static void SetDanglingFlag(CheckedUnsafePtrBaseCheckingEnabled& aBase) { aBase.mIsDangling = true; } }; template class CheckedUnsafePtrBase; template using EnableIfCompatible = std::enable_if_t< std::is_base_of< T, std::remove_reference_t())>>::value, S>; template class CheckedUnsafePtrBase : detail::CheckedUnsafePtrBaseCheckingEnabled { public: MOZ_IMPLICIT constexpr CheckedUnsafePtrBase(const std::nullptr_t = nullptr) : mRawPtr(nullptr) {} template > MOZ_IMPLICIT CheckedUnsafePtrBase(const U& aPtr) { Set(aPtr); } CheckedUnsafePtrBase(const CheckedUnsafePtrBase& aOther) { Set(aOther.Downcast()); } ~CheckedUnsafePtrBase() { Reset(); } CheckedUnsafePtr& operator=(const std::nullptr_t) { Reset(); return Downcast(); } template EnableIfCompatible&> operator=(const U& aPtr) { Replace(aPtr); return Downcast(); } CheckedUnsafePtrBase& operator=(const CheckedUnsafePtrBase& aOther) { if (&aOther != this) { Replace(aOther.Downcast()); } return Downcast(); } constexpr T* get() const { return mRawPtr; } private: template friend class CheckedUnsafePtrBase; CheckedUnsafePtr& Downcast() { return static_cast&>(*this); } const CheckedUnsafePtr& Downcast() const { return static_cast&>(*this); } using Base = detail::CheckedUnsafePtrBaseCheckingEnabled; template void Replace(const U& aPtr) { Reset(); Set(aPtr); } void Reset() { WithCheckedUnsafePtrs( [](Base* const aSelf, detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) { const auto index = aCheckedUnsafePtrs.IndexOf(aSelf); aCheckedUnsafePtrs.UnorderedRemoveElementAt(index); }); mRawPtr = nullptr; } template void Set(const U& aPtr) { this->CopyDanglingFlagIfAvailableFrom(aPtr); mRawPtr = &*aPtr; WithCheckedUnsafePtrs( [](Base* const aSelf, detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) { aCheckedUnsafePtrs.AppendElement(aSelf); }); } template void WithCheckedUnsafePtrs(F&& aClosure) { this->WithCheckedUnsafePtrsImpl(mRawPtr, std::forward(aClosure)); } T* mRawPtr; }; template class CheckedUnsafePtrBase { public: MOZ_IMPLICIT constexpr CheckedUnsafePtrBase(const std::nullptr_t = nullptr) : mRawPtr(nullptr) {} template > MOZ_IMPLICIT constexpr CheckedUnsafePtrBase(const U& aPtr) : mRawPtr(aPtr) {} constexpr CheckedUnsafePtr& operator=(const std::nullptr_t) { mRawPtr = nullptr; return Downcast(); } template constexpr EnableIfCompatible&> operator=( const U& aPtr) { mRawPtr = aPtr; return Downcast(); } constexpr T* get() const { return mRawPtr; } private: constexpr CheckedUnsafePtr& Downcast() { return static_cast&>(*this); } T* mRawPtr; }; } // namespace detail class CheckingPolicyAccess { protected: template static void NotifyCheckFailure(CheckingPolicy& aPolicy) { aPolicy.NotifyCheckFailure(); } }; template class CheckCheckedUnsafePtrs : private CheckingPolicyAccess, private detail::CheckedUnsafePtrBaseAccess { public: using SupportsChecking = std::integral_constant; protected: static constexpr bool ShouldCheck() { static_assert( std::is_base_of::value, "cannot instantiate with a type that's not a subclass of this class"); return true; } void Check(detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) { if (!aCheckedUnsafePtrs.IsEmpty()) { for (auto* const aCheckedUnsafePtrBase : aCheckedUnsafePtrs) { SetDanglingFlag(*aCheckedUnsafePtrBase); } NotifyCheckFailure(*static_cast(this)); } } }; class CrashOnDanglingCheckedUnsafePtr : public CheckCheckedUnsafePtrs { friend class mozilla::CheckingPolicyAccess; void NotifyCheckFailure() { MOZ_CRASH("Found dangling CheckedUnsafePtr"); } }; struct DoNotCheckCheckedUnsafePtrs { using SupportsChecking = std::integral_constant; }; namespace detail { // Template parameter CheckingSupport controls the inclusion of // CheckedUnsafePtrCheckData as a subobject of instantiations of // SupportsCheckedUnsafePtr, ensuring that choosing a policy without checking // support incurs no size overhead. template class SupportCheckedUnsafePtrImpl; template class SupportCheckedUnsafePtrImpl : public CheckingPolicy { protected: template explicit SupportCheckedUnsafePtrImpl(Args&&... aArgs) : CheckingPolicy(std::forward(aArgs)...) {} }; template class SupportCheckedUnsafePtrImpl : public CheckedUnsafePtrCheckData, public CheckingPolicy { template friend class CheckedUnsafePtr; protected: template explicit SupportCheckedUnsafePtrImpl(Args&&... aArgs) : CheckingPolicy(std::forward(aArgs)...) {} ~SupportCheckedUnsafePtrImpl() { if (this->ShouldCheck()) { const auto ptrs = mPtrs.Lock(); this->Check(*ptrs); } } }; struct SupportsCheckedUnsafePtrTag {}; } // namespace detail template using CheckIf = std::conditional_t; using DiagnosticAssertEnabled = std::integral_constant; // A T class that publicly inherits from an instantiation of // SupportsCheckedUnsafePtr and its subclasses can be pointed to by smart // pointers of type CheckedUnsafePtr. Whenever such a smart pointer is // created, its existence is tracked by the pointee according to its // CheckingPolicy. When the pointee goes out of scope it then uses the its // CheckingPolicy to verify that no CheckedUnsafePtr pointers are left pointing // to it. // // The CheckingPolicy type is used to control the kind of verification that // happen at the end of the object's lifetime. By default, debug builds always // check for dangling CheckedUnsafePtr pointers and assert that none are found, // while release builds forgo all checks. (Release builds incur no size or // runtime penalties compared to bare pointers.) template class SupportsCheckedUnsafePtr : public detail::SupportCheckedUnsafePtrImpl, public detail::SupportsCheckedUnsafePtrTag { public: template explicit SupportsCheckedUnsafePtr(Args&&... aArgs) : detail::SupportCheckedUnsafePtrImpl( std::forward(aArgs)...) {} }; // CheckedUnsafePtr is a smart pointer class that helps detect dangling // pointers in cases where such pointers are not allowed. In order to use it, // the pointee T must publicly inherit from an instantiation of // SupportsCheckedUnsafePtr. An CheckedUnsafePtr can be used anywhere a T* // can be used, has the same size, and imposes no additional thread-safety // restrictions. template class CheckedUnsafePtr : public detail::CheckedUnsafePtrBase { static_assert( std::is_base_of::value, "type T must be derived from instantiation of SupportsCheckedUnsafePtr"); public: using detail::CheckedUnsafePtrBase::CheckedUnsafePtrBase; using detail::CheckedUnsafePtrBase::get; constexpr T* operator->() const { return get(); } constexpr T& operator*() const { return *get(); } MOZ_IMPLICIT constexpr operator T*() const { return get(); } template constexpr bool operator==( detail::EnableIfCompatible aRhs) const { return get() == aRhs.get(); } template friend constexpr bool operator==( detail::EnableIfCompatible aLhs, const CheckedUnsafePtr& aRhs) { return aRhs == aLhs; } template constexpr bool operator!=( detail::EnableIfCompatible aRhs) const { return !(*this == aRhs); } template friend constexpr bool operator!=( detail::EnableIfCompatible aLhs, const CheckedUnsafePtr& aRhs) { return aRhs != aLhs; } }; } // namespace mozilla // nsTArray requires by default that T can be safely moved with std::memmove. // Since CheckedUnsafePtr has a non-trivial copy constructor, it has to opt // into nsTArray using them. template struct nsTArray_RelocationStrategy> { using Type = std::conditional_t< T::SupportsChecking::value == mozilla::CheckingSupport::Enabled, nsTArray_RelocateUsingMoveConstructor>, nsTArray_RelocateUsingMemutils>; }; template struct nsTArray_RelocationStrategy< mozilla::NotNull>> { using Type = std::conditional_t>>, nsTArray_RelocateUsingMemutils>; }; #endif // mozilla_CheckedUnsafePtr_h