/* 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 "mozilla/StackWalk.h" #include "mozilla/StaticPrefs_dom.h" #include "nsContentUtils.h" #include "nsTArray.h" #include "nsString.h" #include #include #include #if defined __has_builtin # if __has_builtin(__builtin_FUNCTION) # define bt_function __builtin_FUNCTION() # else # define bt_function "__builtin_FUNCTION() is undefined" # endif # if __has_builtin(__builtin_FILE) # define bt_file __builtin_FILE() # else # define bt_file "__builtin_FILE() is undefined" # endif # if __has_builtin(__builtin_LINE) # define bt_line __builtin_LINE() # else # define bt_line -1 # endif #else # define bt_function "__builtin_FUNCTION() is undefined" # define bt_file "__builtin_FILE() is undefined" # define bt_line -1 #endif namespace mozilla { enum class CheckingSupport { Disabled, Enabled, }; template class CheckedUnsafePtr; namespace detail { static constexpr auto kSourceFileRelativePathMap = std::array, 1>{ {{"mozilla/dom/CheckedUnsafePtr.h"_ns, "dom/quota/CheckedUnsafePtr.h"_ns}}}; static inline nsDependentCSubstring GetSourceFileRelativePath( const nsACString& aSourceFilePath) { static constexpr auto error = "ERROR"_ns; static constexpr auto mozillaRelativeBase = "mozilla/"_ns; static constexpr auto thisSourceFileRelativePath = "/dom/quota/CheckedUnsafePtr.h"_ns; static constexpr auto filePath = nsLiteralCString(__FILE__); MOZ_ASSERT(StringEndsWith(filePath, thisSourceFileRelativePath)); static const auto sourceTreeBase = Substring( filePath, 0, filePath.Length() - thisSourceFileRelativePath.Length()); if (MOZ_LIKELY(StringBeginsWith(aSourceFilePath, sourceTreeBase))) { return Substring(aSourceFilePath, sourceTreeBase.Length() + 1); } // The source file could have been exported to the OBJDIR/dist/include // directory, so we need to check that case as well. static constexpr auto commonHSourceFileRelativePath = "/mozilla/dom/quota/CheckedUnsafePtr.h"_ns; MOZ_ASSERT(StringEndsWith(filePath, commonHSourceFileRelativePath)); static const auto objdirDistIncludeTreeBase = Substring( filePath, 0, filePath.Length() - commonHSourceFileRelativePath.Length()); if (MOZ_LIKELY( StringBeginsWith(aSourceFilePath, objdirDistIncludeTreeBase))) { const auto sourceFileRelativePath = Substring(aSourceFilePath, objdirDistIncludeTreeBase.Length() + 1); // Exported source files don't have to use the same directory structure as // original source files. Check if we have a mapping for the exported // source file. const auto foundIt = std::find_if( kSourceFileRelativePathMap.cbegin(), kSourceFileRelativePathMap.cend(), [&sourceFileRelativePath](const auto& entry) { return entry.first == sourceFileRelativePath; }); if (MOZ_UNLIKELY(foundIt != kSourceFileRelativePathMap.cend())) { return Substring(foundIt->second, 0); } // If we don't have a mapping for it, just remove the mozilla/ prefix // (if there's any). if (MOZ_LIKELY( StringBeginsWith(sourceFileRelativePath, mozillaRelativeBase))) { return Substring(sourceFileRelativePath, mozillaRelativeBase.Length()); } // At this point, we don't know how to transform the relative path of the // exported source file back to the relative path of the original source // file. This can happen when QM_TRY is used in an exported nsIFoo.h file. // If you really need to use QM_TRY there, consider adding a new mapping // for the exported source file. return sourceFileRelativePath; } nsCString::const_iterator begin, end; if (RFindInReadable("/"_ns, aSourceFilePath.BeginReading(begin), aSourceFilePath.EndReading(end))) { // Use the basename as a fallback, to avoid exposing any user parts of the // path. ++begin; return Substring(begin, aSourceFilePath.EndReading(end)); } return nsDependentCSubstring{static_cast>( static_cast(error))}; } static inline void CheckedUnsafePtrStackCallback(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure) { auto* stack = static_cast(aClosure); MozCodeAddressDetails details; MozDescribeCodeAddress(aPC, &details); char buf[1025]; Unused << MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details); stack->Append(buf); stack->Append("\n"); } class CheckedUnsafePtrBaseCheckingEnabled; struct CheckedUnsafePtrCheckData { using Data = nsTArray; DataMutex mPtrs{"mozilla::SupportsCheckedUnsafePtr"}; }; class CheckedUnsafePtrBaseCheckingEnabled { friend class CheckedUnsafePtrBaseAccess; protected: CheckedUnsafePtrBaseCheckingEnabled() = delete; CheckedUnsafePtrBaseCheckingEnabled( const CheckedUnsafePtrBaseCheckingEnabled& aOther) = default; CheckedUnsafePtrBaseCheckingEnabled(const char* aFunction, const char* aFile, const int aLine) : mFunctionName(aFunction), mSourceFile(aFile), mLineNo(aLine) {} // 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); } } void DumpDebugMsg() { fprintf(stderr, "CheckedUnsafePtr [%p]\n", this); fprintf(stderr, "Location of creation: %s, %s:%d\n", mFunctionName.get(), GetSourceFileRelativePath(mSourceFile).BeginReading(), mLineNo); fprintf(stderr, "Stack of creation:\n%s\n", mCreationStack.get()); fprintf(stderr, "Stack of last assignment\n%s\n\n", mLastAssignmentStack.get()); } nsCString mFunctionName{EmptyCString()}; nsCString mSourceFile{EmptyCString()}; int32_t mLineNo{-1}; nsCString mCreationStack{EmptyCString()}; nsCString mLastAssignmentStack{EmptyCString()}; private: bool mIsDangling = false; }; class CheckedUnsafePtrBaseAccess { protected: static void SetDanglingFlag(CheckedUnsafePtrBaseCheckingEnabled& aBase) { aBase.mIsDangling = true; aBase.DumpDebugMsg(); } }; 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, const char* aFunction = bt_function, const char* aFile = bt_file, const int32_t aLine = bt_line) : detail::CheckedUnsafePtrBaseCheckingEnabled(aFunction, aFile, aLine), mRawPtr(nullptr) { if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) { MozStackWalk(CheckedUnsafePtrStackCallback, CallerPC(), 0, &mCreationStack); } } template > MOZ_IMPLICIT CheckedUnsafePtrBase(const U& aPtr, const char* aFunction = bt_function, const char* aFile = bt_file, const int32_t aLine = bt_line) : detail::CheckedUnsafePtrBaseCheckingEnabled(aFunction, aFile, aLine) { if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) { MozStackWalk(CheckedUnsafePtrStackCallback, CallerPC(), 0, &mCreationStack); } Set(aPtr); } CheckedUnsafePtrBase(const CheckedUnsafePtrBase& aOther, const char* aFunction = bt_function, const char* aFile = bt_file, const int32_t aLine = bt_line) : detail::CheckedUnsafePtrBaseCheckingEnabled(aFunction, aFile, aLine) { if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) { MozStackWalk(CheckedUnsafePtrStackCallback, CallerPC(), 0, &mCreationStack); } Set(aOther.Downcast()); } ~CheckedUnsafePtrBase() { Reset(); } CheckedUnsafePtr& operator=(const std::nullptr_t) { // Assign to nullptr, no need to record the last assignment stack. if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) { mLastAssignmentStack.Truncate(); } Reset(); return Downcast(); } template EnableIfCompatible&> operator=(const U& aPtr) { if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) { mLastAssignmentStack.Truncate(); MozStackWalk(CheckedUnsafePtrStackCallback, CallerPC(), 0, &mLastAssignmentStack); } Replace(aPtr); return Downcast(); } CheckedUnsafePtrBase& operator=(const CheckedUnsafePtrBase& aOther) { if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) { mLastAssignmentStack.Truncate(); MozStackWalk(CheckedUnsafePtrStackCallback, CallerPC(), 0, &mLastAssignmentStack); } 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