| 1 | // |
| 2 | // Copyright (c) 2019-2021 Apple, Inc. All rights reserved. |
| 3 | // |
| 4 | // @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
| 5 | // |
| 6 | // This file contains Original Code and/or Modifications of Original Code |
| 7 | // as defined in and that are subject to the Apple Public Source License |
| 8 | // Version 2.0 (the 'License'). You may not use this file except in |
| 9 | // compliance with the License. The rights granted to you under the License |
| 10 | // may not be used to create, or enable the creation or redistribution of, |
| 11 | // unlawful or unlicensed copies of an Apple operating system, or to |
| 12 | // circumvent, violate, or enable the circumvention or violation of, any |
| 13 | // terms of an Apple operating system software license agreement. |
| 14 | // |
| 15 | // Please obtain a copy of the License at |
| 16 | // http://www.opensource.apple.com/apsl/ and read it before using this file. |
| 17 | // |
| 18 | // The Original Code and all software distributed under the License are |
| 19 | // distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
| 20 | // EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
| 21 | // INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
| 22 | // FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
| 23 | // Please see the License for the specific language governing rights and |
| 24 | // limitations under the License. |
| 25 | // |
| 26 | // @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
| 27 | // |
| 28 | |
| 29 | #ifndef XNU_LIBKERN_LIBKERN_CXX_SAFE_ALLOCATION_H |
| 30 | #define XNU_LIBKERN_LIBKERN_CXX_SAFE_ALLOCATION_H |
| 31 | |
| 32 | #if !TAPI |
| 33 | |
| 34 | #include <stddef.h> |
| 35 | #include <stdint.h> |
| 36 | #include <os/base.h> |
| 37 | #if DRIVERKIT_FRAMEWORK_INCLUDE |
| 38 | #include <DriverKit/bounded_ptr.h> |
| 39 | #else |
| 40 | #include <libkern/c++/bounded_ptr.h> |
| 41 | #endif /* DRIVERKIT_FRAMEWORK_INCLUDE */ |
| 42 | |
| 43 | #if (defined(__has_include) && __has_include(<__xnu_libcxx_sentinel.h>) && __has_include(<new>)) |
| 44 | #include <new> |
| 45 | #else |
| 46 | void* operator new(size_t, void*) noexcept; // forward declaration needed for placement-new |
| 47 | #endif |
| 48 | |
| 49 | namespace libkern { |
| 50 | namespace sa_detail { |
| 51 | // TODO: Deduplicate these utilities with other smart pointer utilities |
| 52 | using nullptr_t = decltype(nullptr); |
| 53 | template <typename T> |
| 54 | constexpr bool is_trivially_destructible_v = __is_trivially_destructible(T); |
| 55 | template <typename T> |
| 56 | constexpr bool is_empty_v = __is_empty(T); |
| 57 | template <typename T> |
| 58 | constexpr bool is_nothrow_default_constructible_v = __is_nothrow_constructible(T); |
| 59 | |
| 60 | template <bool Cond, typename T = void> struct enable_if; |
| 61 | template <typename T> struct enable_if<true, T> { using type = T; }; |
| 62 | template <bool Cond, typename T = void> using enable_if_t = typename enable_if<Cond, T>::type; |
| 63 | |
| 64 | template <typename T> struct remove_const { using type = T; }; |
| 65 | template <typename T> struct remove_const<T const> { using type = T; }; |
| 66 | template <typename T> using remove_const_t = typename remove_const<T>::type; |
| 67 | |
| 68 | template <typename T> |
| 69 | void |
| 70 | generic_swap(T& a, T& b) |
| 71 | { |
| 72 | T tmp = a; |
| 73 | a = b; |
| 74 | b = tmp; |
| 75 | } |
| 76 | |
| 77 | template <typename T, enable_if_t<!is_trivially_destructible_v<T> >* = nullptr> |
| 78 | void |
| 79 | destroy(T* first, T* last) |
| 80 | { |
| 81 | for (; first != last; ++first) { |
| 82 | first->~T(); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | template <typename T, enable_if_t<is_trivially_destructible_v<T> >* = nullptr> |
| 87 | void |
| 88 | destroy(T*, T*) |
| 89 | { |
| 90 | // Nothing to do, the elements are trivially destructible |
| 91 | } |
| 92 | |
| 93 | template <typename T> |
| 94 | void |
| 95 | uninitialized_value_construct(T* first, T* last) |
| 96 | { |
| 97 | for (; first != last; ++first) { |
| 98 | ::new (static_cast<void*>(first)) T(); |
| 99 | } |
| 100 | } |
| 101 | } // end namespace sa_detail |
| 102 | |
| 103 | struct adopt_memory_t { |
| 104 | explicit constexpr |
| 105 | adopt_memory_t() = default; |
| 106 | }; |
| 107 | inline constexpr adopt_memory_t adopt_memory{}; |
| 108 | |
| 109 | struct allocate_memory_t { |
| 110 | explicit constexpr |
| 111 | allocate_memory_t() = default; |
| 112 | }; |
| 113 | inline constexpr allocate_memory_t allocate_memory{}; |
| 114 | |
| 115 | struct allocate_memory_zero_t { |
| 116 | explicit constexpr |
| 117 | allocate_memory_zero_t() = default; |
| 118 | }; |
| 119 | inline constexpr allocate_memory_zero_t allocate_memory_zero{}; |
| 120 | |
| 121 | // Lightweight utility class representing a dynamically allocated slab of |
| 122 | // memory, with contiguous objects in it. |
| 123 | // |
| 124 | // The main purpose `safe_allocation` is to: |
| 125 | // 1. Manage a uniquely-owned allocation of memory containing multiple objects |
| 126 | // 2. Check that the allocation is accessed within its bounds on indexing operations |
| 127 | // 3. Act as a source for obtaining (non-owning) `bounded_ptr`s to the underlying memory |
| 128 | // |
| 129 | // In fact, `safe_allocation` should be the primary source of `bounded_ptr`s to |
| 130 | // heap-allocated memory, via its `.begin()` and `.end()` methods. `safe_allocation` |
| 131 | // is optimized for use cases where simple scratch space is needed for calculation |
| 132 | // and deallocated once the calculation is done. As such, it is not a full-blown |
| 133 | // container class, which drives many design choices behind `safe_allocation`: |
| 134 | // |
| 135 | // 1. It can't be copied or compared for equality -- `safe_allocation` is not a proper value type |
| 136 | // 2. It can't be resized -- this keeps the design extremely simple and free of overhead |
| 137 | // 3. You can transfer ownership of `safe_allocation` by using std::move |
| 138 | // |
| 139 | // Design decision: stateless allocators |
| 140 | // ===================================== |
| 141 | // Only allow stateless allocators. While we could technically handle stateful |
| 142 | // allocators (as the C++ Standard Library) does, the benefit of doing so |
| 143 | // compared to the added complexity is absolutely not worth it. Supporting |
| 144 | // stateful allocators everywhere in C++ is regarded (at least in the |
| 145 | // Standardization Committee) as one of the worst design mistakes we've made, |
| 146 | // and so we won't repeat it here. |
| 147 | // |
| 148 | // Design decision: size() is 0 when allocation is null |
| 149 | // ==================================================== |
| 150 | // When the `safe_allocation` is null (because it's been moved-from, or because |
| 151 | // allocation failed, or whatever), we could technically leave the `size_` |
| 152 | // undefined (as long as we make `data_` null). However, this would mean |
| 153 | // that querying the size of the allocation in that case is undefined behavior |
| 154 | // (UB), which is seen as something bad in the context of a type that vends |
| 155 | // itself as safe. So instead, we "overimplement" the type to provide stronger |
| 156 | // guarantees than would be strictly required if performance were the main goal. |
| 157 | template <typename T, typename Allocator, typename TrappingPolicy> |
| 158 | struct safe_allocation { |
| 159 | static_assert(sa_detail::is_empty_v<Allocator>, |
| 160 | "safe_allocation<T, Alloc, ...> requires the Allocator to be stateless" ); |
| 161 | |
| 162 | // Create a null allocation, pointing to no memory. |
| 163 | // |
| 164 | // A null allocation can be destroyed, assigned-to, checked for nullness, |
| 165 | // and otherwise queries for length, but trying to access an element of |
| 166 | // the allocation will fail. |
| 167 | // |
| 168 | // A null allocation basically behaves as an empty array, i.e. `begin()` |
| 169 | // and `end()` will return iterators that are equal and `size()` will |
| 170 | // return `0`. |
| 171 | explicit constexpr safe_allocation() noexcept : data_(nullptr), size_(0) |
| 172 | { |
| 173 | } |
| 174 | |
| 175 | constexpr safe_allocation(sa_detail::nullptr_t) noexcept : safe_allocation() |
| 176 | { |
| 177 | } |
| 178 | |
| 179 | // Create an allocation pointing to already-allocated and initialized memory. |
| 180 | // |
| 181 | // This constructor attaches existing memory to a `safe_allocation`, such |
| 182 | // that it will be released automatically when the `safe_allocation` goes |
| 183 | // out of scope. The objects in that memory must already have been |
| 184 | // initialized, or they must be initialized before the `safe_allocation` |
| 185 | // goes out of scope. |
| 186 | // |
| 187 | // The `n` argument is the number of objects of type `T` in the allocation, |
| 188 | // i.e. `n * sizeof(T)` bytes should have been allocated. |
| 189 | // |
| 190 | // Note that the memory MUST have been allocated with an allocator compatible |
| 191 | // with the `safe_allocation`'s `Allocator`, since the memory will be |
| 192 | // deallocated using that `Allocator`. Bad things will happen if, for |
| 193 | // example, `adopt_memory` is used with memory allocated on the stack: |
| 194 | // the destructor will try to deallocate that memory and will fail to do so. |
| 195 | explicit safe_allocation(T* data, size_t n, adopt_memory_t) : data_(data) |
| 196 | { |
| 197 | if (__improbable(n > UINT32_MAX)) { |
| 198 | TrappingPolicy::trap("safe_allocation size exceeds UINT32_MAX" ); |
| 199 | } |
| 200 | |
| 201 | size_ = static_cast<uint32_t>(n); |
| 202 | } |
| 203 | |
| 204 | // Allocate memory for `n` objects of type `T`, and manage it. |
| 205 | // |
| 206 | // This constructor allocates enough memory for `n` objects of type `T` |
| 207 | // using the `Allocator`, and manages that. Each object in the allocation |
| 208 | // is value-initialized (either set to 0 or the default-constructor called). |
| 209 | // |
| 210 | // If either `n * sizeof(T)` overflows or the allocation fails, the |
| 211 | // resulting `safe_allocation` will be null. It is therefore necessary |
| 212 | // to check whether the allocation is null after using this constructor. |
| 213 | explicit safe_allocation(size_t n, allocate_memory_t) |
| 214 | { |
| 215 | size_t bytes; |
| 216 | if (__improbable(os_mul_overflow(n, sizeof(T), &bytes) || (n > UINT32_MAX))) { |
| 217 | data_ = nullptr; |
| 218 | size_ = 0; |
| 219 | } else { |
| 220 | data_ = reinterpret_cast<T*>(Allocator::allocate(bytes)); |
| 221 | size_ = static_cast<uint32_t>(n); |
| 222 | using RawT = sa_detail::remove_const_t<T>; |
| 223 | RawT* const data = const_cast<RawT*>(data_); |
| 224 | sa_detail::uninitialized_value_construct(data, data + size_); |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | // same as allocate_memory_t variant but allocated data is zero-initialized |
| 229 | explicit safe_allocation(size_t n, allocate_memory_zero_t) |
| 230 | { |
| 231 | static_assert(__is_scalar(T) || __is_aggregate(T), |
| 232 | "Creating objects via zero-allocation requires those objects to be scalars or aggregates (more broadly implicit lifetime types)" ); |
| 233 | size_t bytes; |
| 234 | if (__improbable(os_mul_overflow(n, sizeof(T), &bytes) || (n > UINT32_MAX))) { |
| 235 | data_ = nullptr; |
| 236 | size_ = 0; |
| 237 | } else { |
| 238 | data_ = reinterpret_cast<T*>(Allocator::allocate_zero(bytes)); |
| 239 | size_ = static_cast<uint32_t>(n); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | // A `safe_allocation` can't be copied, because it is not a proper value |
| 244 | // type and it doesn't assume that the elements of the allocation can be |
| 245 | // copied. |
| 246 | safe_allocation(safe_allocation const&) = delete; |
| 247 | safe_allocation& operator=(safe_allocation const&) = delete; |
| 248 | |
| 249 | // Moves the ownership of an allocation from one `safe_allocation` to |
| 250 | // another one. |
| 251 | // |
| 252 | // After this operation, the moved-from `safe_allocation` is null, and |
| 253 | // any iterator into the moved-from `safe_allocation` are now tied to |
| 254 | // the `safe_allocation` that's the target of the assignment, in the |
| 255 | // sense that the iterators will be invalidated when the target of the |
| 256 | // assignment goes out of scope, not when the moved-from allocation |
| 257 | // goes out of scope. |
| 258 | safe_allocation(safe_allocation&& other) noexcept : data_(other.data_), size_(other.size_) |
| 259 | { |
| 260 | other.data_ = nullptr; |
| 261 | other.size_ = 0; |
| 262 | } |
| 263 | |
| 264 | // Clears a `safe_allocation`, making it a null allocation. |
| 265 | // |
| 266 | // If the `safe_allocation` was pointing to valid memory, the objects |
| 267 | // in that memory are destroyed and that memory is freed. |
| 268 | safe_allocation& |
| 269 | operator=(sa_detail::nullptr_t) |
| 270 | { |
| 271 | if (data_ != nullptr) { |
| 272 | destroy_dealloc_(ptr: data_, size: size_); |
| 273 | } |
| 274 | data_ = nullptr; |
| 275 | size_ = 0; |
| 276 | return *this; |
| 277 | } |
| 278 | |
| 279 | // Moves the ownership of an allocation from one `safe_allocation` to |
| 280 | // another one. |
| 281 | // |
| 282 | // After this operation, the moved-from `safe_allocation` is null, and |
| 283 | // any iterator to the moved-from `safe_allocation` obtained before the |
| 284 | // move operation are invalidated. |
| 285 | // |
| 286 | // If the destination `safe_allocation` was pointing to memory before the |
| 287 | // move-assignment, the objects in that memory are destroyed and the |
| 288 | // memory itself is freed. |
| 289 | // |
| 290 | // In case of self-move-assignment, nothing is done. |
| 291 | safe_allocation& |
| 292 | operator=(safe_allocation&& other) |
| 293 | { |
| 294 | if (&other == this) { |
| 295 | return *this; |
| 296 | } |
| 297 | |
| 298 | T* old_data = data_; |
| 299 | size_t old_size = size_; |
| 300 | |
| 301 | data_ = other.data_; |
| 302 | size_ = other.size_; |
| 303 | other.data_ = nullptr; |
| 304 | other.size_ = 0; |
| 305 | |
| 306 | if (old_data != nullptr) { |
| 307 | destroy_dealloc_(ptr: old_data, size: old_size); |
| 308 | } |
| 309 | |
| 310 | return *this; |
| 311 | } |
| 312 | |
| 313 | // Destroys a `safe_allocation`, destroying the objects in it and |
| 314 | // deallocating the underlying memory with the `Allocator`. |
| 315 | // |
| 316 | // If the `safe_allocation` is null, this destructor does nothing. |
| 317 | ~safe_allocation() |
| 318 | { |
| 319 | if (data_ != nullptr) { |
| 320 | destroy_dealloc_(ptr: data_, size: size_); |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | // Returns whether a `safe_allocation` is non-null, i.e. whether it is |
| 325 | // pointing to some memory. |
| 326 | explicit |
| 327 | operator bool() const noexcept |
| 328 | { |
| 329 | return data_ != nullptr; |
| 330 | } |
| 331 | |
| 332 | using iterator = bounded_ptr<T, TrappingPolicy>; |
| 333 | using const_iterator = bounded_ptr<T const, TrappingPolicy>; |
| 334 | |
| 335 | // The following methods allow obtaining iterators (i.e. cursors) to |
| 336 | // objects inside a `safe_allocation`. |
| 337 | // |
| 338 | // The iterators of a `safe_allocation` are `bounded_ptr`s, which know |
| 339 | // the bounds of the allocation and will trap when dereferenced outside |
| 340 | // of those bounds. |
| 341 | // |
| 342 | // `begin()` returns a (const) iterator to the first element in the |
| 343 | // allocation, and `end()` returns a (const) iterator to one-past-the-last |
| 344 | // element in the allocation. The `end()` iterator can't be dereferenced, |
| 345 | // since it is out of bounds. |
| 346 | // |
| 347 | // If the allocation is null, these methods will return null `bounded_ptr`s, |
| 348 | // which can be checked for equality but can't be dereferenced. |
| 349 | OS_ALWAYS_INLINE iterator |
| 350 | begin() noexcept |
| 351 | { |
| 352 | if (data_ == nullptr) { |
| 353 | return iterator(); |
| 354 | } else { |
| 355 | return iterator(data_, data_, data_ + size_); |
| 356 | } |
| 357 | } |
| 358 | OS_ALWAYS_INLINE const_iterator |
| 359 | begin() const noexcept |
| 360 | { |
| 361 | if (data_ == nullptr) { |
| 362 | return const_iterator(); |
| 363 | } else { |
| 364 | return const_iterator(data_, data_, data_ + size_); |
| 365 | } |
| 366 | } |
| 367 | iterator |
| 368 | end() noexcept |
| 369 | { |
| 370 | if (data_ == nullptr) { |
| 371 | return iterator(); |
| 372 | } else { |
| 373 | return iterator(data_ + size_, data_, data_ + size_); |
| 374 | } |
| 375 | } |
| 376 | const_iterator |
| 377 | end() const noexcept |
| 378 | { |
| 379 | if (data_ == nullptr) { |
| 380 | return const_iterator(); |
| 381 | } else { |
| 382 | return const_iterator(data_ + size_, data_, data_ + size_); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | // Returns the number of objects in the allocation. |
| 387 | // |
| 388 | // This method returns `0` if the allocation is null, since such an |
| 389 | // allocation behaves the same as an empty range. |
| 390 | size_t |
| 391 | size() const |
| 392 | { |
| 393 | return size_; |
| 394 | } |
| 395 | |
| 396 | // Returns a non-owning pointer to the underlying memory managed by a |
| 397 | // `safe_allocation`. |
| 398 | // |
| 399 | // This method can be called even if the `safe_allocation` is null, in |
| 400 | // which case the returned pointer will be null. |
| 401 | T* |
| 402 | data() noexcept |
| 403 | { |
| 404 | return data_; |
| 405 | } |
| 406 | T const* |
| 407 | data() const noexcept |
| 408 | { |
| 409 | return data_; |
| 410 | } |
| 411 | |
| 412 | // Access the n-th element of an allocation. |
| 413 | // |
| 414 | // If `n` is out of the bounds of the allocation, this operation will |
| 415 | // trap. If the allocation is null, this operation will trap too. |
| 416 | // |
| 417 | // Design note: |
| 418 | // We voluntarily use a signed type to represent the index even though a |
| 419 | // negative index will always cause a trap. If we used an unsigned type, |
| 420 | // we could get an implicit conversion from signed to unsigned, which |
| 421 | // could silently wrap around. We think trapping early is more likely |
| 422 | // to be helpful in this situation. |
| 423 | OS_ALWAYS_INLINE T& |
| 424 | operator[](ptrdiff_t n) |
| 425 | { |
| 426 | return begin()[n]; // trap happens in `bounded_ptr` if null or OOB |
| 427 | } |
| 428 | OS_ALWAYS_INLINE T const& |
| 429 | operator[](ptrdiff_t n) const |
| 430 | { |
| 431 | return begin()[n]; // trap happens in `bounded_ptr` if null or OOB |
| 432 | } |
| 433 | |
| 434 | private: |
| 435 | // Swap support |
| 436 | friend void |
| 437 | swap(safe_allocation& a, safe_allocation& b) noexcept |
| 438 | { |
| 439 | sa_detail::generic_swap(a.data_, b.data_); |
| 440 | sa_detail::generic_swap(a.size_, b.size_); |
| 441 | } |
| 442 | |
| 443 | static void |
| 444 | destroy_dealloc_(T* ptr, size_t size) |
| 445 | { |
| 446 | sa_detail::destroy(ptr, ptr + size); |
| 447 | // `size * sizeof(T)` can't overflow, because it would have |
| 448 | // overflowed when the allocation was performed otherwise. |
| 449 | using RawT = sa_detail::remove_const_t<T>; |
| 450 | Allocator::deallocate(const_cast<RawT*>(ptr), size * sizeof(T)); |
| 451 | } |
| 452 | |
| 453 | T* data_; |
| 454 | uint32_t size_; |
| 455 | }; |
| 456 | |
| 457 | // The comparison functions against `nullptr` all return whether the allocation |
| 458 | // is null or not. |
| 459 | template <typename T, typename A, typename P> |
| 460 | bool |
| 461 | operator==(safe_allocation<T, A, P> const& x, sa_detail::nullptr_t) |
| 462 | { |
| 463 | return !static_cast<bool>(x); |
| 464 | } |
| 465 | |
| 466 | template <typename T, typename A, typename P> |
| 467 | bool |
| 468 | operator!=(safe_allocation<T, A, P> const& x, sa_detail::nullptr_t) |
| 469 | { |
| 470 | return !(x == nullptr); |
| 471 | } |
| 472 | |
| 473 | template <typename T, typename A, typename P> |
| 474 | bool |
| 475 | operator==(sa_detail::nullptr_t, safe_allocation<T, A, P> const& x) |
| 476 | { |
| 477 | return x == nullptr; |
| 478 | } |
| 479 | |
| 480 | template <typename T, typename A, typename P> |
| 481 | bool |
| 482 | operator!=(sa_detail::nullptr_t, safe_allocation<T, A, P> const& x) |
| 483 | { |
| 484 | return !(x == nullptr); |
| 485 | } |
| 486 | } // end namespace libkern |
| 487 | |
| 488 | #endif /* !TAPI */ |
| 489 | |
| 490 | #endif // !XNU_LIBKERN_LIBKERN_CXX_SAFE_ALLOCATION_H |
| 491 | |