xref: /xnu-8792.81.2/iokit/DriverKit/safe_allocation.h (revision 19c3b8c28c31cb8130e034cfb5df6bf9ba342d90)
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