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