1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Diagnostic class template that helps finding dangling pointers. 6 7 #ifndef mozilla_CheckedUnsafePtr_h 8 #define mozilla_CheckedUnsafePtr_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 #include "mozilla/DataMutex.h" 13 #include "nsTArray.h" 14 15 #include <cstddef> 16 #include <type_traits> 17 #include <utility> 18 19 namespace mozilla { 20 enum class CheckingSupport { 21 Disabled, 22 Enabled, 23 }; 24 25 template <typename T> 26 class CheckedUnsafePtr; 27 28 namespace detail { 29 class CheckedUnsafePtrBaseCheckingEnabled; 30 31 struct CheckedUnsafePtrCheckData { 32 using Data = nsTArray<CheckedUnsafePtrBaseCheckingEnabled*>; 33 34 DataMutex<Data> mPtrs{"mozilla::SupportsCheckedUnsafePtr"}; 35 }; 36 37 class CheckedUnsafePtrBaseCheckingEnabled { 38 friend class CheckedUnsafePtrBaseAccess; 39 40 protected: 41 constexpr CheckedUnsafePtrBaseCheckingEnabled() = default; 42 CheckedUnsafePtrBaseCheckingEnabled( 43 const CheckedUnsafePtrBaseCheckingEnabled& aOther) = default; 44 45 // When copying an CheckedUnsafePtr, its mIsDangling member must be copied as 46 // well; otherwise the new copy might try to dereference a dangling pointer 47 // when destructed. CopyDanglingFlagIfAvailableFrom(const CheckedUnsafePtrBaseCheckingEnabled & aOther)48 void CopyDanglingFlagIfAvailableFrom( 49 const CheckedUnsafePtrBaseCheckingEnabled& aOther) { 50 mIsDangling = aOther.mIsDangling; 51 } 52 53 template <typename Ptr> 54 using DisableForCheckedUnsafePtr = std::enable_if_t< 55 !std::is_base_of<CheckedUnsafePtrBaseCheckingEnabled, Ptr>::value>; 56 57 // When constructing an CheckedUnsafePtr from a different kind of pointer it's 58 // not possible to determine whether it's dangling; therefore it's undefined 59 // behavior to construct one from a dangling pointer, and we assume that any 60 // CheckedUnsafePtr thus constructed is not dangling. 61 template <typename Ptr> CopyDanglingFlagIfAvailableFrom(const Ptr &)62 DisableForCheckedUnsafePtr<Ptr> CopyDanglingFlagIfAvailableFrom(const Ptr&) {} 63 64 template <typename F> WithCheckedUnsafePtrsImpl(CheckedUnsafePtrCheckData * const aRawPtr,F && aClosure)65 void WithCheckedUnsafePtrsImpl(CheckedUnsafePtrCheckData* const aRawPtr, 66 F&& aClosure) { 67 if (!mIsDangling && aRawPtr) { 68 const auto CheckedUnsafePtrs = aRawPtr->mPtrs.Lock(); 69 aClosure(this, *CheckedUnsafePtrs); 70 } 71 } 72 73 private: 74 bool mIsDangling = false; 75 }; 76 77 class CheckedUnsafePtrBaseAccess { 78 protected: SetDanglingFlag(CheckedUnsafePtrBaseCheckingEnabled & aBase)79 static void SetDanglingFlag(CheckedUnsafePtrBaseCheckingEnabled& aBase) { 80 aBase.mIsDangling = true; 81 } 82 }; 83 84 template <typename T, CheckingSupport = T::SupportsChecking::value> 85 class CheckedUnsafePtrBase; 86 87 template <typename T, typename U, typename S = std::nullptr_t> 88 using EnableIfCompatible = std::enable_if_t< 89 std::is_base_of< 90 T, std::remove_reference_t<decltype(*std::declval<U>())>>::value, 91 S>; 92 93 template <typename T> 94 class CheckedUnsafePtrBase<T, CheckingSupport::Enabled> 95 : detail::CheckedUnsafePtrBaseCheckingEnabled { 96 public: 97 MOZ_IMPLICIT constexpr CheckedUnsafePtrBase(const std::nullptr_t = nullptr) mRawPtr(nullptr)98 : mRawPtr(nullptr) {} 99 100 template <typename U, typename = EnableIfCompatible<T, U>> CheckedUnsafePtrBase(const U & aPtr)101 MOZ_IMPLICIT CheckedUnsafePtrBase(const U& aPtr) { 102 Set(aPtr); 103 } 104 CheckedUnsafePtrBase(const CheckedUnsafePtrBase & aOther)105 CheckedUnsafePtrBase(const CheckedUnsafePtrBase& aOther) { 106 Set(aOther.Downcast()); 107 } 108 ~CheckedUnsafePtrBase()109 ~CheckedUnsafePtrBase() { Reset(); } 110 111 CheckedUnsafePtr<T>& operator=(const std::nullptr_t) { 112 Reset(); 113 return Downcast(); 114 } 115 116 template <typename U> 117 EnableIfCompatible<T, U, CheckedUnsafePtr<T>&> operator=(const U& aPtr) { 118 Replace(aPtr); 119 return Downcast(); 120 } 121 122 CheckedUnsafePtrBase& operator=(const CheckedUnsafePtrBase& aOther) { 123 if (&aOther != this) { 124 Replace(aOther.Downcast()); 125 } 126 return Downcast(); 127 } 128 get()129 constexpr T* get() const { return mRawPtr; } 130 131 private: 132 template <typename U, CheckingSupport> 133 friend class CheckedUnsafePtrBase; 134 Downcast()135 CheckedUnsafePtr<T>& Downcast() { 136 return static_cast<CheckedUnsafePtr<T>&>(*this); 137 } Downcast()138 const CheckedUnsafePtr<T>& Downcast() const { 139 return static_cast<const CheckedUnsafePtr<T>&>(*this); 140 } 141 142 using Base = detail::CheckedUnsafePtrBaseCheckingEnabled; 143 144 template <typename U> Replace(const U & aPtr)145 void Replace(const U& aPtr) { 146 Reset(); 147 Set(aPtr); 148 } 149 Reset()150 void Reset() { 151 WithCheckedUnsafePtrs( 152 [](Base* const aSelf, 153 detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) { 154 const auto index = aCheckedUnsafePtrs.IndexOf(aSelf); 155 aCheckedUnsafePtrs.UnorderedRemoveElementAt(index); 156 }); 157 mRawPtr = nullptr; 158 } 159 160 template <typename U> Set(const U & aPtr)161 void Set(const U& aPtr) { 162 this->CopyDanglingFlagIfAvailableFrom(aPtr); 163 mRawPtr = &*aPtr; 164 WithCheckedUnsafePtrs( 165 [](Base* const aSelf, 166 detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) { 167 aCheckedUnsafePtrs.AppendElement(aSelf); 168 }); 169 } 170 171 template <typename F> WithCheckedUnsafePtrs(F && aClosure)172 void WithCheckedUnsafePtrs(F&& aClosure) { 173 this->WithCheckedUnsafePtrsImpl(mRawPtr, std::forward<F>(aClosure)); 174 } 175 176 T* mRawPtr; 177 }; 178 179 template <typename T> 180 class CheckedUnsafePtrBase<T, CheckingSupport::Disabled> { 181 public: 182 MOZ_IMPLICIT constexpr CheckedUnsafePtrBase(const std::nullptr_t = nullptr) mRawPtr(nullptr)183 : mRawPtr(nullptr) {} 184 185 template <typename U, typename = EnableIfCompatible<T, U>> CheckedUnsafePtrBase(const U & aPtr)186 MOZ_IMPLICIT constexpr CheckedUnsafePtrBase(const U& aPtr) : mRawPtr(aPtr) {} 187 188 constexpr CheckedUnsafePtr<T>& operator=(const std::nullptr_t) { 189 mRawPtr = nullptr; 190 return Downcast(); 191 } 192 193 template <typename U> 194 constexpr EnableIfCompatible<T, U, CheckedUnsafePtr<T>&> operator=( 195 const U& aPtr) { 196 mRawPtr = aPtr; 197 return Downcast(); 198 } 199 get()200 constexpr T* get() const { return mRawPtr; } 201 202 private: Downcast()203 constexpr CheckedUnsafePtr<T>& Downcast() { 204 return static_cast<CheckedUnsafePtr<T>&>(*this); 205 } 206 207 T* mRawPtr; 208 }; 209 } // namespace detail 210 211 class CheckingPolicyAccess { 212 protected: 213 template <typename CheckingPolicy> NotifyCheckFailure(CheckingPolicy & aPolicy)214 static void NotifyCheckFailure(CheckingPolicy& aPolicy) { 215 aPolicy.NotifyCheckFailure(); 216 } 217 }; 218 219 template <typename Derived> 220 class CheckCheckedUnsafePtrs : private CheckingPolicyAccess, 221 private detail::CheckedUnsafePtrBaseAccess { 222 public: 223 using SupportsChecking = 224 std::integral_constant<CheckingSupport, CheckingSupport::Enabled>; 225 226 protected: ShouldCheck()227 static constexpr bool ShouldCheck() { 228 static_assert( 229 std::is_base_of<CheckCheckedUnsafePtrs, Derived>::value, 230 "cannot instantiate with a type that's not a subclass of this class"); 231 return true; 232 } 233 Check(detail::CheckedUnsafePtrCheckData::Data & aCheckedUnsafePtrs)234 void Check(detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) { 235 if (!aCheckedUnsafePtrs.IsEmpty()) { 236 for (auto* const aCheckedUnsafePtrBase : aCheckedUnsafePtrs) { 237 SetDanglingFlag(*aCheckedUnsafePtrBase); 238 } 239 NotifyCheckFailure(*static_cast<Derived*>(this)); 240 } 241 } 242 }; 243 244 class CrashOnDanglingCheckedUnsafePtr 245 : public CheckCheckedUnsafePtrs<CrashOnDanglingCheckedUnsafePtr> { 246 friend class mozilla::CheckingPolicyAccess; NotifyCheckFailure()247 void NotifyCheckFailure() { MOZ_CRASH("Found dangling CheckedUnsafePtr"); } 248 }; 249 250 struct DoNotCheckCheckedUnsafePtrs { 251 using SupportsChecking = 252 std::integral_constant<CheckingSupport, CheckingSupport::Disabled>; 253 }; 254 255 namespace detail { 256 // Template parameter CheckingSupport controls the inclusion of 257 // CheckedUnsafePtrCheckData as a subobject of instantiations of 258 // SupportsCheckedUnsafePtr, ensuring that choosing a policy without checking 259 // support incurs no size overhead. 260 template <typename CheckingPolicy, 261 CheckingSupport = CheckingPolicy::SupportsChecking::value> 262 class SupportCheckedUnsafePtrImpl; 263 264 template <typename CheckingPolicy> 265 class SupportCheckedUnsafePtrImpl<CheckingPolicy, CheckingSupport::Disabled> 266 : public CheckingPolicy { 267 protected: 268 template <typename... Args> SupportCheckedUnsafePtrImpl(Args &&...aArgs)269 explicit SupportCheckedUnsafePtrImpl(Args&&... aArgs) 270 : CheckingPolicy(std::forward<Args>(aArgs)...) {} 271 }; 272 273 template <typename CheckingPolicy> 274 class SupportCheckedUnsafePtrImpl<CheckingPolicy, CheckingSupport::Enabled> 275 : public CheckedUnsafePtrCheckData, public CheckingPolicy { 276 template <typename T> 277 friend class CheckedUnsafePtr; 278 279 protected: 280 template <typename... Args> SupportCheckedUnsafePtrImpl(Args &&...aArgs)281 explicit SupportCheckedUnsafePtrImpl(Args&&... aArgs) 282 : CheckingPolicy(std::forward<Args>(aArgs)...) {} 283 ~SupportCheckedUnsafePtrImpl()284 ~SupportCheckedUnsafePtrImpl() { 285 if (this->ShouldCheck()) { 286 const auto ptrs = mPtrs.Lock(); 287 this->Check(*ptrs); 288 } 289 } 290 }; 291 292 struct SupportsCheckedUnsafePtrTag {}; 293 } // namespace detail 294 295 template <typename Condition, 296 typename CheckingPolicy = CrashOnDanglingCheckedUnsafePtr> 297 using CheckIf = std::conditional_t<Condition::value, CheckingPolicy, 298 DoNotCheckCheckedUnsafePtrs>; 299 300 using DiagnosticAssertEnabled = std::integral_constant<bool, 301 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 302 true 303 #else 304 false 305 #endif 306 >; 307 308 // A T class that publicly inherits from an instantiation of 309 // SupportsCheckedUnsafePtr and its subclasses can be pointed to by smart 310 // pointers of type CheckedUnsafePtr<T>. Whenever such a smart pointer is 311 // created, its existence is tracked by the pointee according to its 312 // CheckingPolicy. When the pointee goes out of scope it then uses the its 313 // CheckingPolicy to verify that no CheckedUnsafePtr pointers are left pointing 314 // to it. 315 // 316 // The CheckingPolicy type is used to control the kind of verification that 317 // happen at the end of the object's lifetime. By default, debug builds always 318 // check for dangling CheckedUnsafePtr pointers and assert that none are found, 319 // while release builds forgo all checks. (Release builds incur no size or 320 // runtime penalties compared to bare pointers.) 321 template <typename CheckingPolicy> 322 class SupportsCheckedUnsafePtr 323 : public detail::SupportCheckedUnsafePtrImpl<CheckingPolicy>, 324 public detail::SupportsCheckedUnsafePtrTag { 325 public: 326 template <typename... Args> SupportsCheckedUnsafePtr(Args &&...aArgs)327 explicit SupportsCheckedUnsafePtr(Args&&... aArgs) 328 : detail::SupportCheckedUnsafePtrImpl<CheckingPolicy>( 329 std::forward<Args>(aArgs)...) {} 330 }; 331 332 // CheckedUnsafePtr<T> is a smart pointer class that helps detect dangling 333 // pointers in cases where such pointers are not allowed. In order to use it, 334 // the pointee T must publicly inherit from an instantiation of 335 // SupportsCheckedUnsafePtr. An CheckedUnsafePtr<T> can be used anywhere a T* 336 // can be used, has the same size, and imposes no additional thread-safety 337 // restrictions. 338 template <typename T> 339 class CheckedUnsafePtr : public detail::CheckedUnsafePtrBase<T> { 340 static_assert( 341 std::is_base_of<detail::SupportsCheckedUnsafePtrTag, T>::value, 342 "type T must be derived from instantiation of SupportsCheckedUnsafePtr"); 343 344 public: 345 using detail::CheckedUnsafePtrBase<T>::CheckedUnsafePtrBase; 346 using detail::CheckedUnsafePtrBase<T>::get; 347 348 constexpr T* operator->() const { return get(); } 349 350 constexpr T& operator*() const { return *get(); } 351 352 MOZ_IMPLICIT constexpr operator T*() const { return get(); } 353 354 template <typename U> 355 constexpr bool operator==( 356 detail::EnableIfCompatible<T, U, const U&> aRhs) const { 357 return get() == aRhs.get(); 358 } 359 360 template <typename U> 361 friend constexpr bool operator==( 362 detail::EnableIfCompatible<T, U, const U&> aLhs, 363 const CheckedUnsafePtr& aRhs) { 364 return aRhs == aLhs; 365 } 366 367 template <typename U> 368 constexpr bool operator!=( 369 detail::EnableIfCompatible<T, U, const U&> aRhs) const { 370 return !(*this == aRhs); 371 } 372 373 template <typename U> 374 friend constexpr bool operator!=( 375 detail::EnableIfCompatible<T, U, const U&> aLhs, 376 const CheckedUnsafePtr& aRhs) { 377 return aRhs != aLhs; 378 } 379 }; 380 381 } // namespace mozilla 382 383 // nsTArray<T> requires by default that T can be safely moved with std::memmove. 384 // Since CheckedUnsafePtr<T> has a non-trivial copy constructor, it has to opt 385 // into nsTArray<T> using them. 386 template <typename T> 387 struct nsTArray_RelocationStrategy<mozilla::CheckedUnsafePtr<T>> { 388 using Type = std::conditional_t< 389 T::SupportsChecking::value == mozilla::CheckingSupport::Enabled, 390 nsTArray_RelocateUsingMoveConstructor<mozilla::CheckedUnsafePtr<T>>, 391 nsTArray_RelocateUsingMemutils>; 392 }; 393 394 template <typename T> 395 struct nsTArray_RelocationStrategy< 396 mozilla::NotNull<mozilla::CheckedUnsafePtr<T>>> { 397 using Type = 398 std::conditional_t<T::SupportsChecking::value == 399 mozilla::CheckingSupport::Enabled, 400 nsTArray_RelocateUsingMoveConstructor< 401 mozilla::NotNull<mozilla::CheckedUnsafePtr<T>>>, 402 nsTArray_RelocateUsingMemutils>; 403 }; 404 405 #endif // mozilla_CheckedUnsafePtr_h 406