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"
13 #include "mozilla/StackWalk.h"
14 #include "mozilla/StaticPrefs_dom.h"
15 #include "nsContentUtils.h"
20 #include <type_traits>
23 #if defined __has_builtin
24 # if __has_builtin(__builtin_FUNCTION)
25 # define bt_function __builtin_FUNCTION()
27 # define bt_function "__builtin_FUNCTION() is undefined"
29 # if __has_builtin(__builtin_FILE)
30 # define bt_file __builtin_FILE()
32 # define bt_file "__builtin_FILE() is undefined"
34 # if __has_builtin(__builtin_LINE)
35 # define bt_line __builtin_LINE()
40 # define bt_function "__builtin_FUNCTION() is undefined"
41 # define bt_file "__builtin_FILE() is undefined"
46 enum class CheckingSupport
{
52 class CheckedUnsafePtr
;
56 static constexpr auto kSourceFileRelativePathMap
=
57 std::array
<std::pair
<nsLiteralCString
, nsLiteralCString
>, 1>{
58 {{"mozilla/dom/CheckedUnsafePtr.h"_ns
,
59 "dom/quota/CheckedUnsafePtr.h"_ns
}}};
61 static inline nsDependentCSubstring
GetSourceFileRelativePath(
62 const nsACString
& aSourceFilePath
) {
63 static constexpr auto error
= "ERROR"_ns
;
64 static constexpr auto mozillaRelativeBase
= "mozilla/"_ns
;
65 static constexpr auto thisSourceFileRelativePath
=
66 "/dom/quota/CheckedUnsafePtr.h"_ns
;
67 static constexpr auto filePath
= nsLiteralCString(__FILE__
);
69 MOZ_ASSERT(StringEndsWith(filePath
, thisSourceFileRelativePath
));
70 static const auto sourceTreeBase
= Substring(
71 filePath
, 0, filePath
.Length() - thisSourceFileRelativePath
.Length());
73 if (MOZ_LIKELY(StringBeginsWith(aSourceFilePath
, sourceTreeBase
))) {
74 return Substring(aSourceFilePath
, sourceTreeBase
.Length() + 1);
77 // The source file could have been exported to the OBJDIR/dist/include
78 // directory, so we need to check that case as well.
79 static constexpr auto commonHSourceFileRelativePath
=
80 "/mozilla/dom/quota/CheckedUnsafePtr.h"_ns
;
81 MOZ_ASSERT(StringEndsWith(filePath
, commonHSourceFileRelativePath
));
82 static const auto objdirDistIncludeTreeBase
= Substring(
83 filePath
, 0, filePath
.Length() - commonHSourceFileRelativePath
.Length());
86 StringBeginsWith(aSourceFilePath
, objdirDistIncludeTreeBase
))) {
87 const auto sourceFileRelativePath
=
88 Substring(aSourceFilePath
, objdirDistIncludeTreeBase
.Length() + 1);
90 // Exported source files don't have to use the same directory structure as
91 // original source files. Check if we have a mapping for the exported
93 const auto foundIt
= std::find_if(
94 kSourceFileRelativePathMap
.cbegin(), kSourceFileRelativePathMap
.cend(),
95 [&sourceFileRelativePath
](const auto& entry
) {
96 return entry
.first
== sourceFileRelativePath
;
99 if (MOZ_UNLIKELY(foundIt
!= kSourceFileRelativePathMap
.cend())) {
100 return Substring(foundIt
->second
, 0);
103 // If we don't have a mapping for it, just remove the mozilla/ prefix
106 StringBeginsWith(sourceFileRelativePath
, mozillaRelativeBase
))) {
107 return Substring(sourceFileRelativePath
, mozillaRelativeBase
.Length());
110 // At this point, we don't know how to transform the relative path of the
111 // exported source file back to the relative path of the original source
112 // file. This can happen when QM_TRY is used in an exported nsIFoo.h file.
113 // If you really need to use QM_TRY there, consider adding a new mapping
114 // for the exported source file.
115 return sourceFileRelativePath
;
118 nsCString::const_iterator begin
, end
;
119 if (RFindInReadable("/"_ns
, aSourceFilePath
.BeginReading(begin
),
120 aSourceFilePath
.EndReading(end
))) {
121 // Use the basename as a fallback, to avoid exposing any user parts of the
124 return Substring(begin
, aSourceFilePath
.EndReading(end
));
127 return nsDependentCSubstring
{static_cast<mozilla::Span
<const char>>(
128 static_cast<const nsCString
&>(error
))};
131 static inline void CheckedUnsafePtrStackCallback(uint32_t aFrameNumber
,
132 void* aPC
, void* aSP
,
134 auto* stack
= static_cast<nsCString
*>(aClosure
);
135 MozCodeAddressDetails details
;
136 MozDescribeCodeAddress(aPC
, &details
);
138 Unused
<< MozFormatCodeAddressDetails(buf
, sizeof(buf
), aFrameNumber
, aPC
,
144 class CheckedUnsafePtrBaseCheckingEnabled
;
146 struct CheckedUnsafePtrCheckData
{
147 using Data
= nsTArray
<CheckedUnsafePtrBaseCheckingEnabled
*>;
149 DataMutex
<Data
> mPtrs
{"mozilla::SupportsCheckedUnsafePtr"};
152 class CheckedUnsafePtrBaseCheckingEnabled
{
153 friend class CheckedUnsafePtrBaseAccess
;
156 CheckedUnsafePtrBaseCheckingEnabled() = delete;
157 CheckedUnsafePtrBaseCheckingEnabled(
158 const CheckedUnsafePtrBaseCheckingEnabled
& aOther
) = default;
159 CheckedUnsafePtrBaseCheckingEnabled(const char* aFunction
, const char* aFile
,
161 : mFunctionName(aFunction
), mSourceFile(aFile
), mLineNo(aLine
) {}
163 // When copying an CheckedUnsafePtr, its mIsDangling member must be copied as
164 // well; otherwise the new copy might try to dereference a dangling pointer
166 void CopyDanglingFlagIfAvailableFrom(
167 const CheckedUnsafePtrBaseCheckingEnabled
& aOther
) {
168 mIsDangling
= aOther
.mIsDangling
;
171 template <typename Ptr
>
172 using DisableForCheckedUnsafePtr
= std::enable_if_t
<
173 !std::is_base_of
<CheckedUnsafePtrBaseCheckingEnabled
, Ptr
>::value
>;
175 // When constructing an CheckedUnsafePtr from a different kind of pointer it's
176 // not possible to determine whether it's dangling; therefore it's undefined
177 // behavior to construct one from a dangling pointer, and we assume that any
178 // CheckedUnsafePtr thus constructed is not dangling.
179 template <typename Ptr
>
180 DisableForCheckedUnsafePtr
<Ptr
> CopyDanglingFlagIfAvailableFrom(const Ptr
&) {}
182 template <typename F
>
183 void WithCheckedUnsafePtrsImpl(CheckedUnsafePtrCheckData
* const aRawPtr
,
185 if (!mIsDangling
&& aRawPtr
) {
186 const auto CheckedUnsafePtrs
= aRawPtr
->mPtrs
.Lock();
187 aClosure(this, *CheckedUnsafePtrs
);
191 void DumpDebugMsg() {
192 fprintf(stderr
, "CheckedUnsafePtr [%p]\n", this);
193 fprintf(stderr
, "Location of creation: %s, %s:%d\n", mFunctionName
.get(),
194 GetSourceFileRelativePath(mSourceFile
).BeginReading(), mLineNo
);
195 fprintf(stderr
, "Stack of creation:\n%s\n", mCreationStack
.get());
196 fprintf(stderr
, "Stack of last assignment\n%s\n\n",
197 mLastAssignmentStack
.get());
200 nsCString mFunctionName
{EmptyCString()};
201 nsCString mSourceFile
{EmptyCString()};
203 nsCString mCreationStack
{EmptyCString()};
204 nsCString mLastAssignmentStack
{EmptyCString()};
207 bool mIsDangling
= false;
210 class CheckedUnsafePtrBaseAccess
{
212 static void SetDanglingFlag(CheckedUnsafePtrBaseCheckingEnabled
& aBase
) {
213 aBase
.mIsDangling
= true;
214 aBase
.DumpDebugMsg();
218 template <typename T
, CheckingSupport
= T::SupportsChecking::value
>
219 class CheckedUnsafePtrBase
;
221 template <typename T
, typename U
, typename S
= std::nullptr_t
>
222 using EnableIfCompatible
= std::enable_if_t
<
224 T
, std::remove_reference_t
<decltype(*std::declval
<U
>())>>::value
,
227 template <typename T
>
228 class CheckedUnsafePtrBase
<T
, CheckingSupport::Enabled
>
229 : detail::CheckedUnsafePtrBaseCheckingEnabled
{
231 MOZ_IMPLICIT
constexpr CheckedUnsafePtrBase(
232 const std::nullptr_t
= nullptr, const char* aFunction
= bt_function
,
233 const char* aFile
= bt_file
, const int32_t aLine
= bt_line
)
234 : detail::CheckedUnsafePtrBaseCheckingEnabled(aFunction
, aFile
, aLine
),
236 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
237 MozStackWalk(CheckedUnsafePtrStackCallback
, CallerPC(), 0,
242 template <typename U
, typename
= EnableIfCompatible
<T
, U
>>
243 MOZ_IMPLICIT
CheckedUnsafePtrBase(const U
& aPtr
,
244 const char* aFunction
= bt_function
,
245 const char* aFile
= bt_file
,
246 const int32_t aLine
= bt_line
)
247 : detail::CheckedUnsafePtrBaseCheckingEnabled(aFunction
, aFile
, aLine
) {
248 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
249 MozStackWalk(CheckedUnsafePtrStackCallback
, CallerPC(), 0,
255 CheckedUnsafePtrBase(const CheckedUnsafePtrBase
& aOther
,
256 const char* aFunction
= bt_function
,
257 const char* aFile
= bt_file
,
258 const int32_t aLine
= bt_line
)
259 : detail::CheckedUnsafePtrBaseCheckingEnabled(aFunction
, aFile
, aLine
) {
260 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
261 MozStackWalk(CheckedUnsafePtrStackCallback
, CallerPC(), 0,
264 Set(aOther
.Downcast());
267 ~CheckedUnsafePtrBase() { Reset(); }
269 CheckedUnsafePtr
<T
>& operator=(const std::nullptr_t
) {
270 // Assign to nullptr, no need to record the last assignment stack.
271 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
272 mLastAssignmentStack
.Truncate();
278 template <typename U
>
279 EnableIfCompatible
<T
, U
, CheckedUnsafePtr
<T
>&> operator=(const U
& aPtr
) {
280 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
281 mLastAssignmentStack
.Truncate();
282 MozStackWalk(CheckedUnsafePtrStackCallback
, CallerPC(), 0,
283 &mLastAssignmentStack
);
289 CheckedUnsafePtrBase
& operator=(const CheckedUnsafePtrBase
& aOther
) {
290 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
291 mLastAssignmentStack
.Truncate();
292 MozStackWalk(CheckedUnsafePtrStackCallback
, CallerPC(), 0,
293 &mLastAssignmentStack
);
295 if (&aOther
!= this) {
296 Replace(aOther
.Downcast());
301 constexpr T
* get() const { return mRawPtr
; }
304 template <typename U
, CheckingSupport
>
305 friend class CheckedUnsafePtrBase
;
307 CheckedUnsafePtr
<T
>& Downcast() {
308 return static_cast<CheckedUnsafePtr
<T
>&>(*this);
310 const CheckedUnsafePtr
<T
>& Downcast() const {
311 return static_cast<const CheckedUnsafePtr
<T
>&>(*this);
314 using Base
= detail::CheckedUnsafePtrBaseCheckingEnabled
;
316 template <typename U
>
317 void Replace(const U
& aPtr
) {
323 WithCheckedUnsafePtrs(
324 [](Base
* const aSelf
,
325 detail::CheckedUnsafePtrCheckData::Data
& aCheckedUnsafePtrs
) {
326 const auto index
= aCheckedUnsafePtrs
.IndexOf(aSelf
);
327 aCheckedUnsafePtrs
.UnorderedRemoveElementAt(index
);
332 template <typename U
>
333 void Set(const U
& aPtr
) {
334 this->CopyDanglingFlagIfAvailableFrom(aPtr
);
336 WithCheckedUnsafePtrs(
337 [](Base
* const aSelf
,
338 detail::CheckedUnsafePtrCheckData::Data
& aCheckedUnsafePtrs
) {
339 aCheckedUnsafePtrs
.AppendElement(aSelf
);
343 template <typename F
>
344 void WithCheckedUnsafePtrs(F
&& aClosure
) {
345 this->WithCheckedUnsafePtrsImpl(mRawPtr
, std::forward
<F
>(aClosure
));
351 template <typename T
>
352 class CheckedUnsafePtrBase
<T
, CheckingSupport::Disabled
> {
354 MOZ_IMPLICIT
constexpr CheckedUnsafePtrBase(const std::nullptr_t
= nullptr)
355 : mRawPtr(nullptr) {}
357 template <typename U
, typename
= EnableIfCompatible
<T
, U
>>
358 MOZ_IMPLICIT
constexpr CheckedUnsafePtrBase(const U
& aPtr
) : mRawPtr(aPtr
) {}
360 constexpr CheckedUnsafePtr
<T
>& operator=(const std::nullptr_t
) {
365 template <typename U
>
366 constexpr EnableIfCompatible
<T
, U
, CheckedUnsafePtr
<T
>&> operator=(
372 constexpr T
* get() const { return mRawPtr
; }
375 constexpr CheckedUnsafePtr
<T
>& Downcast() {
376 return static_cast<CheckedUnsafePtr
<T
>&>(*this);
381 } // namespace detail
383 class CheckingPolicyAccess
{
385 template <typename CheckingPolicy
>
386 static void NotifyCheckFailure(CheckingPolicy
& aPolicy
) {
387 aPolicy
.NotifyCheckFailure();
391 template <typename Derived
>
392 class CheckCheckedUnsafePtrs
: private CheckingPolicyAccess
,
393 private detail::CheckedUnsafePtrBaseAccess
{
395 using SupportsChecking
=
396 std::integral_constant
<CheckingSupport
, CheckingSupport::Enabled
>;
399 static constexpr bool ShouldCheck() {
401 std::is_base_of
<CheckCheckedUnsafePtrs
, Derived
>::value
,
402 "cannot instantiate with a type that's not a subclass of this class");
406 void Check(detail::CheckedUnsafePtrCheckData::Data
& aCheckedUnsafePtrs
) {
407 if (!aCheckedUnsafePtrs
.IsEmpty()) {
408 for (auto* const aCheckedUnsafePtrBase
: aCheckedUnsafePtrs
) {
409 SetDanglingFlag(*aCheckedUnsafePtrBase
);
411 NotifyCheckFailure(*static_cast<Derived
*>(this));
416 class CrashOnDanglingCheckedUnsafePtr
417 : public CheckCheckedUnsafePtrs
<CrashOnDanglingCheckedUnsafePtr
> {
418 friend class mozilla::CheckingPolicyAccess
;
419 void NotifyCheckFailure() { MOZ_CRASH("Found dangling CheckedUnsafePtr"); }
422 struct DoNotCheckCheckedUnsafePtrs
{
423 using SupportsChecking
=
424 std::integral_constant
<CheckingSupport
, CheckingSupport::Disabled
>;
428 // Template parameter CheckingSupport controls the inclusion of
429 // CheckedUnsafePtrCheckData as a subobject of instantiations of
430 // SupportsCheckedUnsafePtr, ensuring that choosing a policy without checking
431 // support incurs no size overhead.
432 template <typename CheckingPolicy
,
433 CheckingSupport
= CheckingPolicy::SupportsChecking::value
>
434 class SupportCheckedUnsafePtrImpl
;
436 template <typename CheckingPolicy
>
437 class SupportCheckedUnsafePtrImpl
<CheckingPolicy
, CheckingSupport::Disabled
>
438 : public CheckingPolicy
{
440 template <typename
... Args
>
441 explicit SupportCheckedUnsafePtrImpl(Args
&&... aArgs
)
442 : CheckingPolicy(std::forward
<Args
>(aArgs
)...) {}
445 template <typename CheckingPolicy
>
446 class SupportCheckedUnsafePtrImpl
<CheckingPolicy
, CheckingSupport::Enabled
>
447 : public CheckedUnsafePtrCheckData
, public CheckingPolicy
{
448 template <typename T
>
449 friend class CheckedUnsafePtr
;
452 template <typename
... Args
>
453 explicit SupportCheckedUnsafePtrImpl(Args
&&... aArgs
)
454 : CheckingPolicy(std::forward
<Args
>(aArgs
)...) {}
456 ~SupportCheckedUnsafePtrImpl() {
457 if (this->ShouldCheck()) {
458 const auto ptrs
= mPtrs
.Lock();
464 struct SupportsCheckedUnsafePtrTag
{};
465 } // namespace detail
467 template <typename Condition
,
468 typename CheckingPolicy
= CrashOnDanglingCheckedUnsafePtr
>
469 using CheckIf
= std::conditional_t
<Condition::value
, CheckingPolicy
,
470 DoNotCheckCheckedUnsafePtrs
>;
472 using DiagnosticAssertEnabled
= std::integral_constant
<bool,
473 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
480 // A T class that publicly inherits from an instantiation of
481 // SupportsCheckedUnsafePtr and its subclasses can be pointed to by smart
482 // pointers of type CheckedUnsafePtr<T>. Whenever such a smart pointer is
483 // created, its existence is tracked by the pointee according to its
484 // CheckingPolicy. When the pointee goes out of scope it then uses the its
485 // CheckingPolicy to verify that no CheckedUnsafePtr pointers are left pointing
488 // The CheckingPolicy type is used to control the kind of verification that
489 // happen at the end of the object's lifetime. By default, debug builds always
490 // check for dangling CheckedUnsafePtr pointers and assert that none are found,
491 // while release builds forgo all checks. (Release builds incur no size or
492 // runtime penalties compared to bare pointers.)
493 template <typename CheckingPolicy
>
494 class SupportsCheckedUnsafePtr
495 : public detail::SupportCheckedUnsafePtrImpl
<CheckingPolicy
>,
496 public detail::SupportsCheckedUnsafePtrTag
{
498 template <typename
... Args
>
499 explicit SupportsCheckedUnsafePtr(Args
&&... aArgs
)
500 : detail::SupportCheckedUnsafePtrImpl
<CheckingPolicy
>(
501 std::forward
<Args
>(aArgs
)...) {}
504 // CheckedUnsafePtr<T> is a smart pointer class that helps detect dangling
505 // pointers in cases where such pointers are not allowed. In order to use it,
506 // the pointee T must publicly inherit from an instantiation of
507 // SupportsCheckedUnsafePtr. An CheckedUnsafePtr<T> can be used anywhere a T*
508 // can be used, has the same size, and imposes no additional thread-safety
510 template <typename T
>
511 class CheckedUnsafePtr
: public detail::CheckedUnsafePtrBase
<T
> {
513 std::is_base_of
<detail::SupportsCheckedUnsafePtrTag
, T
>::value
,
514 "type T must be derived from instantiation of SupportsCheckedUnsafePtr");
517 using detail::CheckedUnsafePtrBase
<T
>::CheckedUnsafePtrBase
;
518 using detail::CheckedUnsafePtrBase
<T
>::get
;
520 constexpr T
* operator->() const { return get(); }
522 constexpr T
& operator*() const { return *get(); }
524 MOZ_IMPLICIT
constexpr operator T
*() const { return get(); }
526 template <typename U
>
527 constexpr bool operator==(
528 detail::EnableIfCompatible
<T
, U
, const U
&> aRhs
) const {
529 return get() == aRhs
.get();
532 template <typename U
>
533 friend constexpr bool operator==(
534 detail::EnableIfCompatible
<T
, U
, const U
&> aLhs
,
535 const CheckedUnsafePtr
& aRhs
) {
539 template <typename U
>
540 constexpr bool operator!=(
541 detail::EnableIfCompatible
<T
, U
, const U
&> aRhs
) const {
542 return !(*this == aRhs
);
545 template <typename U
>
546 friend constexpr bool operator!=(
547 detail::EnableIfCompatible
<T
, U
, const U
&> aLhs
,
548 const CheckedUnsafePtr
& aRhs
) {
553 } // namespace mozilla
555 // nsTArray<T> requires by default that T can be safely moved with std::memmove.
556 // Since CheckedUnsafePtr<T> has a non-trivial copy constructor, it has to opt
557 // into nsTArray<T> using them.
558 template <typename T
>
559 struct nsTArray_RelocationStrategy
<mozilla::CheckedUnsafePtr
<T
>> {
560 using Type
= std::conditional_t
<
561 T::SupportsChecking::value
== mozilla::CheckingSupport::Enabled
,
562 nsTArray_RelocateUsingMoveConstructor
<mozilla::CheckedUnsafePtr
<T
>>,
563 nsTArray_RelocateUsingMemutils
>;
566 template <typename T
>
567 struct nsTArray_RelocationStrategy
<
568 mozilla::NotNull
<mozilla::CheckedUnsafePtr
<T
>>> {
570 std::conditional_t
<T::SupportsChecking::value
==
571 mozilla::CheckingSupport::Enabled
,
572 nsTArray_RelocateUsingMoveConstructor
<
573 mozilla::NotNull
<mozilla::CheckedUnsafePtr
<T
>>>,
574 nsTArray_RelocateUsingMemutils
>;
577 #endif // mozilla_CheckedUnsafePtr_h