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_(data_, 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_(old_data, 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_(data_, 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