xref: /xnu-11417.140.69/iokit/DriverKit/safe_allocation.h (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
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