Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / dom / quota / CheckedUnsafePtr.h
blob9d233d84d918bfdf3a1e6db74884347170c3e521
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"
16 #include "nsTArray.h"
17 #include "nsString.h"
19 #include <cstddef>
20 #include <type_traits>
21 #include <utility>
23 #if defined __has_builtin
24 # if __has_builtin(__builtin_FUNCTION)
25 # define bt_function __builtin_FUNCTION()
26 # else
27 # define bt_function "__builtin_FUNCTION() is undefined"
28 # endif
29 # if __has_builtin(__builtin_FILE)
30 # define bt_file __builtin_FILE()
31 # else
32 # define bt_file "__builtin_FILE() is undefined"
33 # endif
34 # if __has_builtin(__builtin_LINE)
35 # define bt_line __builtin_LINE()
36 # else
37 # define bt_line -1
38 # endif
39 #else
40 # define bt_function "__builtin_FUNCTION() is undefined"
41 # define bt_file "__builtin_FILE() is undefined"
42 # define bt_line -1
43 #endif
45 namespace mozilla {
46 enum class CheckingSupport {
47 Disabled,
48 Enabled,
51 template <typename T>
52 class CheckedUnsafePtr;
54 namespace detail {
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());
85 if (MOZ_LIKELY(
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
92 // source file.
93 const auto foundIt = std::find_if(
94 kSourceFileRelativePathMap.cbegin(), kSourceFileRelativePathMap.cend(),
95 [&sourceFileRelativePath](const auto& entry) {
96 return entry.first == sourceFileRelativePath;
97 });
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
104 // (if there's any).
105 if (MOZ_LIKELY(
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
122 // path.
123 ++begin;
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,
133 void* aClosure) {
134 auto* stack = static_cast<nsCString*>(aClosure);
135 MozCodeAddressDetails details;
136 MozDescribeCodeAddress(aPC, &details);
137 char buf[1025];
138 Unused << MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC,
139 &details);
140 stack->Append(buf);
141 stack->Append("\n");
144 class CheckedUnsafePtrBaseCheckingEnabled;
146 struct CheckedUnsafePtrCheckData {
147 using Data = nsTArray<CheckedUnsafePtrBaseCheckingEnabled*>;
149 DataMutex<Data> mPtrs{"mozilla::SupportsCheckedUnsafePtr"};
152 class CheckedUnsafePtrBaseCheckingEnabled {
153 friend class CheckedUnsafePtrBaseAccess;
155 protected:
156 CheckedUnsafePtrBaseCheckingEnabled() = delete;
157 CheckedUnsafePtrBaseCheckingEnabled(
158 const CheckedUnsafePtrBaseCheckingEnabled& aOther) = default;
159 CheckedUnsafePtrBaseCheckingEnabled(const char* aFunction, const char* aFile,
160 const int aLine)
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
165 // when destructed.
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,
184 F&& aClosure) {
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()};
202 int32_t mLineNo{-1};
203 nsCString mCreationStack{EmptyCString()};
204 nsCString mLastAssignmentStack{EmptyCString()};
206 private:
207 bool mIsDangling = false;
210 class CheckedUnsafePtrBaseAccess {
211 protected:
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<
223 std::is_base_of<
224 T, std::remove_reference_t<decltype(*std::declval<U>())>>::value,
227 template <typename T>
228 class CheckedUnsafePtrBase<T, CheckingSupport::Enabled>
229 : detail::CheckedUnsafePtrBaseCheckingEnabled {
230 public:
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),
235 mRawPtr(nullptr) {
236 if (StaticPrefs::dom_checkedUnsafePtr_dumpStacks_enabled()) {
237 MozStackWalk(CheckedUnsafePtrStackCallback, CallerPC(), 0,
238 &mCreationStack);
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,
250 &mCreationStack);
252 Set(aPtr);
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,
262 &mCreationStack);
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();
274 Reset();
275 return Downcast();
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);
285 Replace(aPtr);
286 return Downcast();
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());
298 return Downcast();
301 constexpr T* get() const { return mRawPtr; }
303 private:
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) {
318 Reset();
319 Set(aPtr);
322 void Reset() {
323 WithCheckedUnsafePtrs(
324 [](Base* const aSelf,
325 detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) {
326 const auto index = aCheckedUnsafePtrs.IndexOf(aSelf);
327 aCheckedUnsafePtrs.UnorderedRemoveElementAt(index);
329 mRawPtr = nullptr;
332 template <typename U>
333 void Set(const U& aPtr) {
334 this->CopyDanglingFlagIfAvailableFrom(aPtr);
335 mRawPtr = &*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));
348 T* mRawPtr;
351 template <typename T>
352 class CheckedUnsafePtrBase<T, CheckingSupport::Disabled> {
353 public:
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) {
361 mRawPtr = nullptr;
362 return Downcast();
365 template <typename U>
366 constexpr EnableIfCompatible<T, U, CheckedUnsafePtr<T>&> operator=(
367 const U& aPtr) {
368 mRawPtr = aPtr;
369 return Downcast();
372 constexpr T* get() const { return mRawPtr; }
374 private:
375 constexpr CheckedUnsafePtr<T>& Downcast() {
376 return static_cast<CheckedUnsafePtr<T>&>(*this);
379 T* mRawPtr;
381 } // namespace detail
383 class CheckingPolicyAccess {
384 protected:
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 {
394 public:
395 using SupportsChecking =
396 std::integral_constant<CheckingSupport, CheckingSupport::Enabled>;
398 protected:
399 static constexpr bool ShouldCheck() {
400 static_assert(
401 std::is_base_of<CheckCheckedUnsafePtrs, Derived>::value,
402 "cannot instantiate with a type that's not a subclass of this class");
403 return true;
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>;
427 namespace detail {
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 {
439 protected:
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;
451 protected:
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();
459 this->Check(*ptrs);
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
474 true
475 #else
476 false
477 #endif
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
486 // to it.
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 {
497 public:
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
509 // restrictions.
510 template <typename T>
511 class CheckedUnsafePtr : public detail::CheckedUnsafePtrBase<T> {
512 static_assert(
513 std::is_base_of<detail::SupportsCheckedUnsafePtrTag, T>::value,
514 "type T must be derived from instantiation of SupportsCheckedUnsafePtr");
516 public:
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) {
536 return aRhs == aLhs;
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) {
549 return aRhs != aLhs;
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>>> {
569 using Type =
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