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/. */
5 // Diagnostic class template that helps finding dangling pointers.
7 #ifndef mozilla_CheckedUnsafePtr_h
8 #define mozilla_CheckedUnsafePtr_h
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/DataMutex.h"
16 #include <type_traits>
20 enum class CheckingSupport
{
26 class CheckedUnsafePtr
;
29 class CheckedUnsafePtrBaseCheckingEnabled
;
31 struct CheckedUnsafePtrCheckData
{
32 using Data
= nsTArray
<CheckedUnsafePtrBaseCheckingEnabled
*>;
34 DataMutex
<Data
> mPtrs
{"mozilla::SupportsCheckedUnsafePtr"};
37 class CheckedUnsafePtrBaseCheckingEnabled
{
38 friend class CheckedUnsafePtrBaseAccess
;
41 constexpr CheckedUnsafePtrBaseCheckingEnabled() = default;
42 CheckedUnsafePtrBaseCheckingEnabled(
43 const CheckedUnsafePtrBaseCheckingEnabled
& aOther
) = default;
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
48 void CopyDanglingFlagIfAvailableFrom(
49 const CheckedUnsafePtrBaseCheckingEnabled
& aOther
) {
50 mIsDangling
= aOther
.mIsDangling
;
53 template <typename Ptr
>
54 using DisableForCheckedUnsafePtr
= std::enable_if_t
<
55 !std::is_base_of
<CheckedUnsafePtrBaseCheckingEnabled
, Ptr
>::value
>;
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
>
62 DisableForCheckedUnsafePtr
<Ptr
> CopyDanglingFlagIfAvailableFrom(const Ptr
&) {}
65 void WithCheckedUnsafePtrsImpl(CheckedUnsafePtrCheckData
* const aRawPtr
,
67 if (!mIsDangling
&& aRawPtr
) {
68 const auto CheckedUnsafePtrs
= aRawPtr
->mPtrs
.Lock();
69 aClosure(this, *CheckedUnsafePtrs
);
74 bool mIsDangling
= false;
77 class CheckedUnsafePtrBaseAccess
{
79 static void SetDanglingFlag(CheckedUnsafePtrBaseCheckingEnabled
& aBase
) {
80 aBase
.mIsDangling
= true;
84 template <typename T
, CheckingSupport
= T::SupportsChecking::value
>
85 class CheckedUnsafePtrBase
;
87 template <typename T
, typename U
, typename S
= std::nullptr_t
>
88 using EnableIfCompatible
=
89 std::enable_if_t
<std::is_base_of
<T
, std::remove_reference_t
<decltype(
90 *std::declval
<U
>())>>::value
,
94 class CheckedUnsafePtrBase
<T
, CheckingSupport::Enabled
>
95 : detail::CheckedUnsafePtrBaseCheckingEnabled
{
97 MOZ_IMPLICIT
constexpr CheckedUnsafePtrBase(const std::nullptr_t
= nullptr)
100 template <typename U
, typename
= EnableIfCompatible
<T
, U
>>
101 MOZ_IMPLICIT
CheckedUnsafePtrBase(const U
& aPtr
) {
105 CheckedUnsafePtrBase(const CheckedUnsafePtrBase
& aOther
) {
106 Set(aOther
.Downcast());
109 ~CheckedUnsafePtrBase() { Reset(); }
111 CheckedUnsafePtr
<T
>& operator=(const std::nullptr_t
) {
116 template <typename U
>
117 EnableIfCompatible
<T
, U
, CheckedUnsafePtr
<T
>&> operator=(const U
& aPtr
) {
122 CheckedUnsafePtrBase
& operator=(const CheckedUnsafePtrBase
& aOther
) {
123 if (&aOther
!= this) {
124 Replace(aOther
.Downcast());
129 constexpr T
* get() const { return mRawPtr
; }
132 template <typename U
, CheckingSupport
>
133 friend class CheckedUnsafePtrBase
;
135 CheckedUnsafePtr
<T
>& Downcast() {
136 return static_cast<CheckedUnsafePtr
<T
>&>(*this);
138 const CheckedUnsafePtr
<T
>& Downcast() const {
139 return static_cast<const CheckedUnsafePtr
<T
>&>(*this);
142 using Base
= detail::CheckedUnsafePtrBaseCheckingEnabled
;
144 template <typename U
>
145 void Replace(const U
& aPtr
) {
151 WithCheckedUnsafePtrs(
152 [](Base
* const aSelf
,
153 detail::CheckedUnsafePtrCheckData::Data
& aCheckedUnsafePtrs
) {
154 const auto index
= aCheckedUnsafePtrs
.IndexOf(aSelf
);
155 aCheckedUnsafePtrs
.UnorderedRemoveElementAt(index
);
160 template <typename U
>
161 void Set(const U
& aPtr
) {
162 this->CopyDanglingFlagIfAvailableFrom(aPtr
);
164 WithCheckedUnsafePtrs(
165 [](Base
* const aSelf
,
166 detail::CheckedUnsafePtrCheckData::Data
& aCheckedUnsafePtrs
) {
167 aCheckedUnsafePtrs
.AppendElement(aSelf
);
171 template <typename F
>
172 void WithCheckedUnsafePtrs(F
&& aClosure
) {
173 this->WithCheckedUnsafePtrsImpl(mRawPtr
, std::forward
<F
>(aClosure
));
179 template <typename T
>
180 class CheckedUnsafePtrBase
<T
, CheckingSupport::Disabled
> {
182 MOZ_IMPLICIT
constexpr CheckedUnsafePtrBase(const std::nullptr_t
= nullptr)
183 : mRawPtr(nullptr) {}
185 template <typename U
, typename
= EnableIfCompatible
<T
, U
>>
186 MOZ_IMPLICIT
constexpr CheckedUnsafePtrBase(const U
& aPtr
) : mRawPtr(aPtr
) {}
188 constexpr CheckedUnsafePtr
<T
>& operator=(const std::nullptr_t
) {
193 template <typename U
>
194 constexpr EnableIfCompatible
<T
, U
, CheckedUnsafePtr
<T
>&> operator=(
200 constexpr T
* get() const { return mRawPtr
; }
203 constexpr CheckedUnsafePtr
<T
>& Downcast() {
204 return static_cast<CheckedUnsafePtr
<T
>&>(*this);
209 } // namespace detail
211 class CheckingPolicyAccess
{
213 template <typename CheckingPolicy
>
214 static void NotifyCheckFailure(CheckingPolicy
& aPolicy
) {
215 aPolicy
.NotifyCheckFailure();
219 template <typename Derived
>
220 class CheckCheckedUnsafePtrs
: private CheckingPolicyAccess
,
221 private detail::CheckedUnsafePtrBaseAccess
{
223 using SupportsChecking
=
224 std::integral_constant
<CheckingSupport
, CheckingSupport::Enabled
>;
227 static constexpr bool ShouldCheck() {
229 std::is_base_of
<CheckCheckedUnsafePtrs
, Derived
>::value
,
230 "cannot instantiate with a type that's not a subclass of this class");
234 void Check(detail::CheckedUnsafePtrCheckData::Data
& aCheckedUnsafePtrs
) {
235 if (!aCheckedUnsafePtrs
.IsEmpty()) {
236 for (auto* const aCheckedUnsafePtrBase
: aCheckedUnsafePtrs
) {
237 SetDanglingFlag(*aCheckedUnsafePtrBase
);
239 NotifyCheckFailure(*static_cast<Derived
*>(this));
244 class CrashOnDanglingCheckedUnsafePtr
245 : public CheckCheckedUnsafePtrs
<CrashOnDanglingCheckedUnsafePtr
> {
246 friend class mozilla::CheckingPolicyAccess
;
247 void NotifyCheckFailure() { MOZ_CRASH("Found dangling CheckedUnsafePtr"); }
250 struct DoNotCheckCheckedUnsafePtrs
{
251 using SupportsChecking
=
252 std::integral_constant
<CheckingSupport
, CheckingSupport::Disabled
>;
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
;
264 template <typename CheckingPolicy
>
265 class SupportCheckedUnsafePtrImpl
<CheckingPolicy
, CheckingSupport::Disabled
>
266 : public CheckingPolicy
{
268 template <typename
... Args
>
269 explicit SupportCheckedUnsafePtrImpl(Args
&&... aArgs
)
270 : CheckingPolicy(std::forward
<Args
>(aArgs
)...) {}
273 template <typename CheckingPolicy
>
274 class SupportCheckedUnsafePtrImpl
<CheckingPolicy
, CheckingSupport::Enabled
>
275 : public CheckedUnsafePtrCheckData
, public CheckingPolicy
{
276 template <typename T
>
277 friend class CheckedUnsafePtr
;
280 template <typename
... Args
>
281 explicit SupportCheckedUnsafePtrImpl(Args
&&... aArgs
)
282 : CheckingPolicy(std::forward
<Args
>(aArgs
)...) {}
284 ~SupportCheckedUnsafePtrImpl() {
285 if (this->ShouldCheck()) {
286 const auto ptrs
= mPtrs
.Lock();
292 struct SupportsCheckedUnsafePtrTag
{};
293 } // namespace detail
295 template <typename Condition
,
296 typename CheckingPolicy
= CrashOnDanglingCheckedUnsafePtr
>
297 using CheckIf
= std::conditional_t
<Condition::value
, CheckingPolicy
,
298 DoNotCheckCheckedUnsafePtrs
>;
300 using DiagnosticAssertEnabled
= std::integral_constant
<bool,
301 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
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
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
{
326 template <typename
... Args
>
327 explicit SupportsCheckedUnsafePtr(Args
&&... aArgs
)
328 : detail::SupportCheckedUnsafePtrImpl
<CheckingPolicy
>(
329 std::forward
<Args
>(aArgs
)...) {}
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
338 template <typename T
>
339 class CheckedUnsafePtr
: public detail::CheckedUnsafePtrBase
<T
> {
341 std::is_base_of
<detail::SupportsCheckedUnsafePtrTag
, T
>::value
,
342 "type T must be derived from instantiation of SupportsCheckedUnsafePtr");
345 using detail::CheckedUnsafePtrBase
<T
>::CheckedUnsafePtrBase
;
346 using detail::CheckedUnsafePtrBase
<T
>::get
;
348 constexpr T
* operator->() const { return get(); }
350 constexpr T
& operator*() const { return *get(); }
352 MOZ_IMPLICIT
constexpr operator T
*() const { return get(); }
354 template <typename U
>
355 constexpr bool operator==(
356 detail::EnableIfCompatible
<T
, U
, const U
&> aRhs
) const {
357 return get() == aRhs
.get();
360 template <typename U
>
361 friend constexpr bool operator==(
362 detail::EnableIfCompatible
<T
, U
, const U
&> aLhs
,
363 const CheckedUnsafePtr
& aRhs
) {
367 template <typename U
>
368 constexpr bool operator!=(
369 detail::EnableIfCompatible
<T
, U
, const U
&> aRhs
) const {
370 return !(*this == aRhs
);
373 template <typename U
>
374 friend constexpr bool operator!=(
375 detail::EnableIfCompatible
<T
, U
, const U
&> aLhs
,
376 const CheckedUnsafePtr
& aRhs
) {
381 } // namespace mozilla
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
>;
394 template <typename T
>
395 struct nsTArray_RelocationStrategy
<
396 mozilla::NotNull
<mozilla::CheckedUnsafePtr
<T
>>> {
398 std::conditional_t
<T::SupportsChecking::value
==
399 mozilla::CheckingSupport::Enabled
,
400 nsTArray_RelocateUsingMoveConstructor
<
401 mozilla::NotNull
<mozilla::CheckedUnsafePtr
<T
>>>,
402 nsTArray_RelocateUsingMemutils
>;
405 #endif // mozilla_CheckedUnsafePtr_h