NotNull.h (15864B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_NotNull_h 8 #define mozilla_NotNull_h 9 10 // It's often unclear if a particular pointer, be it raw (T*) or smart 11 // (RefPtr<T>, nsCOMPtr<T>, etc.) can be null. This leads to missing null 12 // checks (which can cause crashes) and unnecessary null checks (which clutter 13 // the code). 14 // 15 // C++ has a built-in alternative that avoids these problems: references. This 16 // module defines another alternative, NotNull, which can be used in cases 17 // where references are not suitable. 18 // 19 // In the comments below we use the word "handle" to cover all varieties of 20 // pointers and references. 21 // 22 // References 23 // ---------- 24 // References are always non-null. (You can do |T& r = *p;| where |p| is null, 25 // but that's undefined behaviour. C++ doesn't provide any built-in, ironclad 26 // guarantee of non-nullness.) 27 // 28 // A reference works well when you need a temporary handle to an existing 29 // single object, e.g. for passing a handle to a function, or as a local handle 30 // within another object. (In Rust parlance, this is a "borrow".) 31 // 32 // A reference is less appropriate in the following cases. 33 // 34 // - As a primary handle to an object. E.g. code such as this is possible but 35 // strange: |T& t = *new T(); ...; delete &t;| 36 // 37 // - As a handle to an array. It's common for |T*| to refer to either a single 38 // |T| or an array of |T|, but |T&| cannot refer to an array of |T| because 39 // you can't index off a reference (at least, not without first converting it 40 // to a pointer). 41 // 42 // - When the handle identity is meaningful, e.g. if you have a hashtable of 43 // handles, because you have to use |&| on the reference to convert it to a 44 // pointer. 45 // 46 // - Some people don't like using non-const references as function parameters, 47 // because it is not clear at the call site that the argument might be 48 // modified. 49 // 50 // - When you need "smart" behaviour. E.g. we lack reference equivalents to 51 // RefPtr and nsCOMPtr. 52 // 53 // - When interfacing with code that uses pointers a lot, sometimes using a 54 // reference just feels like an odd fit. 55 // 56 // Furthermore, a reference is impossible in the following cases. 57 // 58 // - When the handle is rebound to another object. References don't allow this. 59 // 60 // - When the handle has type |void|. |void&| is not allowed. 61 // 62 // NotNull is an alternative that can be used in any of the above cases except 63 // for the last one, where the handle type is |void|. See below. 64 65 #include <cstddef> 66 #include <type_traits> 67 #include <utility> 68 69 #include "mozilla/Assertions.h" 70 71 namespace mozilla { 72 73 namespace detail { 74 template <typename T> 75 struct CopyablePtr { 76 T mPtr; 77 78 template <typename U> 79 explicit CopyablePtr(U&& aPtr) : mPtr{std::forward<U>(aPtr)} {} 80 81 template <typename U> 82 explicit CopyablePtr(CopyablePtr<U> aPtr) : mPtr{std::move(aPtr.mPtr)} {} 83 }; 84 } // namespace detail 85 86 template <typename T> 87 class MovingNotNull; 88 89 // NotNull can be used to wrap a "base" pointer (raw or smart) to indicate it 90 // is not null. Some examples: 91 // 92 // - NotNull<char*> 93 // - NotNull<RefPtr<Event>> 94 // - NotNull<nsCOMPtr<Event>> 95 // - NotNull<UniquePtr<Pointee>> 96 // 97 // NotNull has the following notable properties. 98 // 99 // - It has zero space overhead. 100 // 101 // - It must be initialized explicitly. There is no default initialization. 102 // 103 // - It auto-converts to the base pointer type. 104 // 105 // - It does not auto-convert from a base pointer. Implicit conversion from a 106 // less-constrained type (e.g. T*) to a more-constrained type (e.g. 107 // NotNull<T*>) is dangerous. Creation and assignment from a base pointer can 108 // only be done with WrapNotNull() or MakeNotNull<>(), which makes them 109 // impossible to overlook, both when writing and reading code. 110 // 111 // - When initialized (or assigned) it is checked, and if it is null we abort. 112 // This guarantees that it cannot be null. 113 // 114 // - |operator bool()| is deleted. This means you cannot check a NotNull in a 115 // boolean context, which eliminates the possibility of unnecessary null 116 // checks. 117 // 118 // - It is not movable, but copyable if the base pointer type is copyable. It 119 // may be used together with MovingNotNull to avoid unnecessary copies or when 120 // the base pointer type is not copyable (such as UniquePtr<T>). 121 // 122 template <typename T> 123 class NotNull { 124 template <typename U> 125 friend constexpr NotNull<U> WrapNotNull(U aBasePtr); 126 template <typename U> 127 friend constexpr NotNull<U> WrapNotNullUnchecked(U aBasePtr); 128 template <typename U, typename... Args> 129 friend constexpr NotNull<U> MakeNotNull(Args&&... aArgs); 130 template <typename U> 131 friend class NotNull; 132 133 detail::CopyablePtr<T> mBasePtr; 134 135 // This constructor is only used by WrapNotNull() and MakeNotNull<U>(). 136 template <typename U> 137 constexpr explicit NotNull(U aBasePtr) : mBasePtr(T{std::move(aBasePtr)}) { 138 static_assert(sizeof(T) == sizeof(NotNull<T>), 139 "NotNull must have zero space overhead."); 140 static_assert(offsetof(NotNull<T>, mBasePtr) == 0, 141 "mBasePtr must have zero offset."); 142 } 143 144 public: 145 // Disallow default construction. 146 NotNull() = delete; 147 148 // Construct/assign from another NotNull with a compatible base pointer type. 149 template <typename U, 150 typename = std::enable_if_t<std::is_convertible_v<const U&, T>>> 151 constexpr MOZ_IMPLICIT NotNull(const NotNull<U>& aOther) 152 : mBasePtr(aOther.mBasePtr) {} 153 154 template <typename U, 155 typename = std::enable_if_t<std::is_convertible_v<U&&, T>>> 156 constexpr MOZ_IMPLICIT NotNull(MovingNotNull<U>&& aOther) 157 : mBasePtr(std::move(aOther).unwrapBasePtr()) {} 158 159 // Disallow null checks, which are unnecessary for this type. 160 explicit operator bool() const = delete; 161 162 // Explicit conversion to a base pointer. Use only to resolve ambiguity or to 163 // get a castable pointer. 164 constexpr const T& get() const { return mBasePtr.mPtr; } 165 166 // Implicit conversion to a base pointer. Preferable to get(). 167 constexpr operator const T&() const { return get(); } 168 169 // Implicit conversion to a raw pointer from const lvalue-reference if 170 // supported by the base pointer (for RefPtr<T> -> T* compatibility). 171 template <typename U, 172 std::enable_if_t<!std::is_pointer_v<T> && 173 std::is_convertible_v<const T&, U*>, 174 int> = 0> 175 constexpr operator U*() const& { 176 return get(); 177 } 178 179 // Don't allow implicit conversions to raw pointers from rvalue-references. 180 template <typename U, 181 std::enable_if_t<!std::is_pointer_v<T> && 182 std::is_convertible_v<const T&, U*> && 183 !std::is_convertible_v<const T&&, U*>, 184 int> = 0> 185 constexpr operator U*() const&& = delete; 186 187 // Dereference operators. 188 constexpr auto* operator->() const MOZ_NONNULL_RETURN { 189 return mBasePtr.mPtr.operator->(); 190 } 191 constexpr decltype(*mBasePtr.mPtr) operator*() const { 192 return *mBasePtr.mPtr; 193 } 194 195 // NotNull can be copied, but not moved. Moving a NotNull with a smart base 196 // pointer would leave a nullptr NotNull behind. The move operations must not 197 // be explicitly deleted though, since that would cause overload resolution to 198 // fail in situations where a copy is possible. 199 NotNull(const NotNull&) = default; 200 NotNull& operator=(const NotNull&) = default; 201 }; 202 203 // Specialization for T* to allow adding MOZ_NONNULL_RETURN attributes. 204 template <typename T> 205 class NotNull<T*> { 206 template <typename U> 207 friend constexpr NotNull<U> WrapNotNull(U aBasePtr); 208 template <typename U> 209 friend constexpr NotNull<U*> WrapNotNullUnchecked(U* aBasePtr); 210 template <typename U, typename... Args> 211 friend constexpr NotNull<U> MakeNotNull(Args&&... aArgs); 212 template <typename U> 213 friend class NotNull; 214 215 T* mBasePtr; 216 217 // This constructor is only used by WrapNotNull() and MakeNotNull<U>(). 218 template <typename U> 219 constexpr explicit NotNull(U* aBasePtr) : mBasePtr(aBasePtr) {} 220 221 public: 222 // Disallow default construction. 223 NotNull() = delete; 224 225 // Construct/assign from another NotNull with a compatible base pointer type. 226 template <typename U, 227 typename = std::enable_if_t<std::is_convertible_v<const U&, T*>>> 228 constexpr MOZ_IMPLICIT NotNull(const NotNull<U>& aOther) 229 : mBasePtr(aOther.get()) { 230 static_assert(sizeof(T*) == sizeof(NotNull<T*>), 231 "NotNull must have zero space overhead."); 232 static_assert(offsetof(NotNull<T*>, mBasePtr) == 0, 233 "mBasePtr must have zero offset."); 234 } 235 236 template <typename U, 237 typename = std::enable_if_t<std::is_convertible_v<U&&, T*>>> 238 constexpr MOZ_IMPLICIT NotNull(MovingNotNull<U>&& aOther) 239 : mBasePtr(NotNull{std::move(aOther)}) {} 240 241 // Disallow null checks, which are unnecessary for this type. 242 explicit operator bool() const = delete; 243 244 // Explicit conversion to a base pointer. Use only to resolve ambiguity or to 245 // get a castable pointer. 246 constexpr T* get() const MOZ_NONNULL_RETURN { return mBasePtr; } 247 248 // Implicit conversion to a base pointer. Preferable to get(). 249 constexpr operator T*() const MOZ_NONNULL_RETURN { return get(); } 250 251 // Dereference operators. 252 constexpr T* operator->() const MOZ_NONNULL_RETURN { return get(); } 253 constexpr T& operator*() const { return *mBasePtr; } 254 }; 255 256 template <typename T> 257 constexpr NotNull<T> WrapNotNull(T aBasePtr) { 258 MOZ_RELEASE_ASSERT(aBasePtr); 259 return NotNull<T>{std::move(aBasePtr)}; 260 } 261 262 // WrapNotNullUnchecked should only be used in situations, where it is 263 // statically known that aBasePtr is non-null, and redundant release assertions 264 // should be avoided. It is only defined for raw base pointers, since it is only 265 // needed for those right now. There is no fundamental reason not to allow 266 // arbitrary base pointers here. 267 template <typename T> 268 constexpr NotNull<T> WrapNotNullUnchecked(T aBasePtr) { 269 return NotNull<T>{std::move(aBasePtr)}; 270 } 271 272 template <typename T> 273 MOZ_NONNULL(1) 274 constexpr NotNull<T*> WrapNotNullUnchecked(T* const aBasePtr) { 275 #if defined(__clang__) 276 # pragma clang diagnostic push 277 # pragma clang diagnostic ignored "-Wpointer-bool-conversion" 278 #elif defined(__GNUC__) 279 # pragma GCC diagnostic push 280 # pragma GCC diagnostic ignored "-Wnonnull-compare" 281 #endif 282 MOZ_ASSERT(aBasePtr); 283 #if defined(__clang__) 284 # pragma clang diagnostic pop 285 #elif defined(__GNUC__) 286 # pragma GCC diagnostic pop 287 #endif 288 return NotNull<T*>{aBasePtr}; 289 } 290 291 // A variant of NotNull that can be used as a return value or parameter type and 292 // moved into both NotNull and non-NotNull targets. This is not possible with 293 // NotNull, as it is not movable. MovingNotNull can therefore not guarantee it 294 // is always non-nullptr, but it can't be dereferenced, and there are debug 295 // assertions that ensure it is only moved once. 296 template <typename T> 297 class MOZ_NON_AUTOABLE MovingNotNull { 298 template <typename U> 299 friend constexpr MovingNotNull<U> WrapMovingNotNullUnchecked(U aBasePtr); 300 301 T mBasePtr; 302 #ifdef DEBUG 303 bool mConsumed = false; 304 #endif 305 306 // This constructor is only used by WrapNotNull() and MakeNotNull<U>(). 307 template <typename U> 308 constexpr explicit MovingNotNull(U aBasePtr) : mBasePtr{std::move(aBasePtr)} { 309 #ifndef DEBUG 310 static_assert(sizeof(T) == sizeof(MovingNotNull<T>), 311 "NotNull must have zero space overhead."); 312 #endif 313 static_assert(offsetof(MovingNotNull<T>, mBasePtr) == 0, 314 "mBasePtr must have zero offset."); 315 } 316 317 public: 318 MovingNotNull() = delete; 319 320 MOZ_IMPLICIT MovingNotNull(const NotNull<T>& aSrc) : mBasePtr(aSrc.get()) {} 321 322 template <typename U, 323 typename = std::enable_if_t<std::is_convertible_v<U, T>>> 324 MOZ_IMPLICIT MovingNotNull(const NotNull<U>& aSrc) : mBasePtr(aSrc.get()) {} 325 326 template <typename U, 327 typename = std::enable_if_t<std::is_convertible_v<U, T>>> 328 MOZ_IMPLICIT MovingNotNull(MovingNotNull<U>&& aSrc) 329 : mBasePtr(std::move(aSrc).unwrapBasePtr()) {} 330 331 MOZ_IMPLICIT operator T() && { return std::move(*this).unwrapBasePtr(); } 332 333 MOZ_IMPLICIT operator NotNull<T>() && { return std::move(*this).unwrap(); } 334 335 NotNull<T> unwrap() && { 336 return WrapNotNullUnchecked(std::move(*this).unwrapBasePtr()); 337 } 338 339 T unwrapBasePtr() && { 340 #ifdef DEBUG 341 MOZ_ASSERT(!mConsumed); 342 mConsumed = true; 343 #endif 344 return std::move(mBasePtr); 345 } 346 347 MovingNotNull(MovingNotNull&&) = default; 348 MovingNotNull& operator=(MovingNotNull&&) = default; 349 }; 350 351 template <typename T> 352 constexpr MovingNotNull<T> WrapMovingNotNullUnchecked(T aBasePtr) { 353 return MovingNotNull<T>{std::move(aBasePtr)}; 354 } 355 356 template <typename T> 357 constexpr MovingNotNull<T> WrapMovingNotNull(T aBasePtr) { 358 MOZ_RELEASE_ASSERT(aBasePtr); 359 return WrapMovingNotNullUnchecked(std::move(aBasePtr)); 360 } 361 362 namespace detail { 363 364 // Extract the pointed-to type from a pointer type (be it raw or smart). 365 // The default implementation uses the dereferencing operator of the pointer 366 // type to find what it's pointing to. 367 template <typename Pointer> 368 struct PointedTo { 369 // Remove the reference that dereferencing operators may return. 370 using Type = std::remove_reference_t<decltype(*std::declval<Pointer>())>; 371 using NonConstType = std::remove_const_t<Type>; 372 }; 373 374 // Specializations for raw pointers. 375 // This is especially required because VS 2017 15.6 (March 2018) started 376 // rejecting the above `decltype(*std::declval<Pointer>())` trick for raw 377 // pointers. 378 // See bug 1443367. 379 template <typename T> 380 struct PointedTo<T*> { 381 using Type = T; 382 using NonConstType = T; 383 }; 384 385 template <typename T> 386 struct PointedTo<const T*> { 387 using Type = const T; 388 using NonConstType = T; 389 }; 390 391 } // namespace detail 392 393 // Allocate an object with infallible new, and wrap its pointer in NotNull. 394 // |MakeNotNull<Ptr<Ob>>(args...)| will run |new Ob(args...)| 395 // and return NotNull<Ptr<Ob>>. 396 template <typename T, typename... Args> 397 constexpr NotNull<T> MakeNotNull(Args&&... aArgs) { 398 using Pointee = typename detail::PointedTo<T>::NonConstType; 399 static_assert(!std::is_array_v<Pointee>, 400 "MakeNotNull cannot construct an array"); 401 return NotNull<T>(new Pointee(std::forward<Args>(aArgs)...)); 402 } 403 404 // Compare two NotNulls. 405 template <typename T, typename U> 406 constexpr bool operator==(const NotNull<T>& aLhs, const NotNull<U>& aRhs) { 407 return aLhs.get() == aRhs.get(); 408 } 409 template <typename T, typename U> 410 constexpr bool operator!=(const NotNull<T>& aLhs, const NotNull<U>& aRhs) { 411 return aLhs.get() != aRhs.get(); 412 } 413 414 // Compare a NotNull to a base pointer. 415 template <typename T, typename U> 416 constexpr bool operator==(const NotNull<T>& aLhs, const U& aRhs) { 417 return aLhs.get() == aRhs; 418 } 419 template <typename T, typename U> 420 constexpr bool operator!=(const NotNull<T>& aLhs, const U& aRhs) { 421 return aLhs.get() != aRhs; 422 } 423 424 // Compare a base pointer to a NotNull. 425 template <typename T, typename U> 426 constexpr bool operator==(const T& aLhs, const NotNull<U>& aRhs) { 427 return aLhs == aRhs.get(); 428 } 429 template <typename T, typename U> 430 constexpr bool operator!=(const T& aLhs, const NotNull<U>& aRhs) { 431 return aLhs != aRhs.get(); 432 } 433 434 // Disallow comparing a NotNull to a nullptr. 435 template <typename T> 436 bool operator==(const NotNull<T>&, std::nullptr_t) = delete; 437 template <typename T> 438 bool operator!=(const NotNull<T>&, std::nullptr_t) = delete; 439 440 // Disallow comparing a nullptr to a NotNull. 441 template <typename T> 442 bool operator==(std::nullptr_t, const NotNull<T>&) = delete; 443 template <typename T> 444 bool operator!=(std::nullptr_t, const NotNull<T>&) = delete; 445 446 } // namespace mozilla 447 448 #endif /* mozilla_NotNull_h */