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