Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
//
// Copyright (c) 2019-2021 Apple, Inc. All rights reserved.
//
// @APPLE_OSREFERENCE_LICENSE_HEADER_START@
//
// This file contains Original Code and/or Modifications of Original Code
// as defined in and that are subject to the Apple Public Source License
// Version 2.0 (the 'License'). You may not use this file except in
// compliance with the License. The rights granted to you under the License
// may not be used to create, or enable the creation or redistribution of,
// unlawful or unlicensed copies of an Apple operating system, or to
// circumvent, violate, or enable the circumvention or violation of, any
// terms of an Apple operating system software license agreement.
//
// Please obtain a copy of the License at
// http://www.opensource.apple.com/apsl/ and read it before using this file.
//
// The Original Code and all software distributed under the License are
// distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
// INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
// Please see the License for the specific language governing rights and
// limitations under the License.
//
// @APPLE_OSREFERENCE_LICENSE_HEADER_END@
//

#ifndef XNU_LIBKERN_LIBKERN_CXX_SAFE_ALLOCATION_H
#define XNU_LIBKERN_LIBKERN_CXX_SAFE_ALLOCATION_H

#if !TAPI

#include <stddef.h>
#include <stdint.h>
#include <os/base.h>
#if DRIVERKIT_FRAMEWORK_INCLUDE
#include <DriverKit/bounded_ptr.h>
#else
#include <libkern/c++/bounded_ptr.h>
#endif /* DRIVERKIT_FRAMEWORK_INCLUDE */

void* operator new(size_t, void*) noexcept; // forward declaration needed for placement-new

namespace libkern {
namespace sa_detail {
// TODO: Deduplicate these utilities with other smart pointer utilities
using nullptr_t = decltype(nullptr);
template <typename T>
constexpr bool is_trivially_destructible_v = __is_trivially_destructible(T);
template <typename T>
constexpr bool is_empty_v = __is_empty(T);
template <typename T>
constexpr bool is_nothrow_default_constructible_v = __is_nothrow_constructible(T);

template <bool Cond, typename T = void> struct enable_if;
template <typename T> struct enable_if<true, T> { using type = T; };
template <bool Cond, typename T = void> using enable_if_t = typename enable_if<Cond, T>::type;

template <typename T> struct remove_const { using type = T; };
template <typename T> struct remove_const<T const> { using type = T; };
template <typename T> using remove_const_t = typename remove_const<T>::type;

template <typename T>
void
generic_swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

template <typename T, enable_if_t<!is_trivially_destructible_v<T> >* = nullptr>
void
destroy(T* first, T* last)
{
	for (; first != last; ++first) {
		first->~T();
	}
}

template <typename T, enable_if_t<is_trivially_destructible_v<T> >* = nullptr>
void
destroy(T*, T*)
{
	// Nothing to do, the elements are trivially destructible
}

template <typename T>
void
uninitialized_value_construct(T* first, T* last)
{
	for (; first != last; ++first) {
		::new (static_cast<void*>(first)) T();
	}
}
} // end namespace sa_detail

struct adopt_memory_t {
	explicit constexpr
	adopt_memory_t() = default;
};
inline constexpr adopt_memory_t adopt_memory{};

struct allocate_memory_t {
	explicit constexpr
	allocate_memory_t() = default;
};
inline constexpr allocate_memory_t allocate_memory{};

struct allocate_memory_zero_t {
	explicit constexpr
	allocate_memory_zero_t() = default;
};
inline constexpr allocate_memory_zero_t allocate_memory_zero{};

// Lightweight utility class representing a dynamically allocated slab of
// memory, with contiguous objects in it.
//
// The main purpose `safe_allocation` is to:
// 1. Manage a uniquely-owned allocation of memory containing multiple objects
// 2. Check that the allocation is accessed within its bounds on indexing operations
// 3. Act as a source for obtaining (non-owning) `bounded_ptr`s to the underlying memory
//
// In fact, `safe_allocation` should be the primary source of `bounded_ptr`s to
// heap-allocated memory, via its `.begin()` and `.end()` methods. `safe_allocation`
// is optimized for use cases where simple scratch space is needed for calculation
// and deallocated once the calculation is done. As such, it is not a full-blown
// container class, which drives many design choices behind `safe_allocation`:
//
// 1. It can't be copied or compared for equality -- `safe_allocation` is not a proper value type
// 2. It can't be resized -- this keeps the design extremely simple and free of overhead
// 3. You can transfer ownership of `safe_allocation` by using std::move
//
// Design decision: stateless allocators
// =====================================
// Only allow stateless allocators. While we could technically handle stateful
// allocators (as the C++ Standard Library) does, the benefit of doing so
// compared to the added complexity is absolutely not worth it. Supporting
// stateful allocators everywhere in C++ is regarded (at least in the
// Standardization Committee) as one of the worst design mistakes we've made,
// and so we won't repeat it here.
//
// Design decision: size() is 0 when allocation is null
// ====================================================
// When the `safe_allocation` is null (because it's been moved-from, or because
// allocation failed, or whatever), we could technically leave the `size_`
// undefined (as long as we make `data_` null). However, this would mean
// that querying the size of the allocation in that case is undefined behavior
// (UB), which is seen as something bad in the context of a type that vends
// itself as safe. So instead, we "overimplement" the type to provide stronger
// guarantees than would be strictly required if performance were the main goal.
template <typename T, typename Allocator, typename TrappingPolicy>
struct safe_allocation {
	static_assert(sa_detail::is_empty_v<Allocator>,
	    "safe_allocation<T, Alloc, ...> requires the Allocator to be stateless");

	// Create a null allocation, pointing to no memory.
	//
	// A null allocation can be destroyed, assigned-to, checked for nullness,
	// and otherwise queries for length, but trying to access an element of
	// the allocation will fail.
	//
	// A null allocation basically behaves as an empty array, i.e. `begin()`
	// and `end()` will return iterators that are equal and `size()` will
	// return `0`.
	explicit constexpr safe_allocation() noexcept : data_(nullptr), size_(0)
	{
	}

	constexpr safe_allocation(sa_detail::nullptr_t) noexcept : safe_allocation()
	{
	}

	// Create an allocation pointing to already-allocated and initialized memory.
	//
	// This constructor attaches existing memory to a `safe_allocation`, such
	// that it will be released automatically when the `safe_allocation` goes
	// out of scope. The objects in that memory must already have been
	// initialized, or they must be initialized before the `safe_allocation`
	// goes out of scope.
	//
	// The `n` argument is the number of objects of type `T` in the allocation,
	// i.e. `n * sizeof(T)` bytes should have been allocated.
	//
	// Note that the memory MUST have been allocated with an allocator compatible
	// with the `safe_allocation`'s `Allocator`, since the memory will be
	// deallocated using that `Allocator`. Bad things will happen if, for
	// example, `adopt_memory` is used with memory allocated on the stack:
	// the destructor will try to deallocate that memory and will fail to do so.
	explicit safe_allocation(T* data, size_t n, adopt_memory_t) : data_(data)
	{
		if (__improbable(n > UINT32_MAX)) {
			TrappingPolicy::trap("safe_allocation size exceeds UINT32_MAX");
		}

		size_ = static_cast<uint32_t>(n);
	}

	// Allocate memory for `n` objects of type `T`, and manage it.
	//
	// This constructor allocates enough memory for `n` objects of type `T`
	// using the `Allocator`, and manages that. Each object in the allocation
	// is value-initialized (either set to 0 or the default-constructor called).
	//
	// If either `n * sizeof(T)` overflows or the allocation fails, the
	// resulting `safe_allocation` will be null. It is therefore necessary
	// to check whether the allocation is null after using this constructor.
	explicit safe_allocation(size_t n, allocate_memory_t)
	{
		size_t bytes;
		if (__improbable(os_mul_overflow(n, sizeof(T), &bytes) || (n > UINT32_MAX))) {
			data_ = nullptr;
			size_ = 0;
		} else {
			data_ = reinterpret_cast<T*>(Allocator::allocate(bytes));
			size_ = static_cast<uint32_t>(n);
			using RawT = sa_detail::remove_const_t<T>;
			RawT* const data = const_cast<RawT*>(data_);
			sa_detail::uninitialized_value_construct(data, data + size_);
		}
	}

	// same as allocate_memory_t variant but allocated data is zero-initialized
	explicit safe_allocation(size_t n, allocate_memory_zero_t)
	{
		static_assert(__is_scalar(T) || __is_aggregate(T),
		    "Creating objects via zero-allocation requires those objects to be scalars or aggregates (more broadly implicit lifetime types)");
		size_t bytes;
		if (__improbable(os_mul_overflow(n, sizeof(T), &bytes) || (n > UINT32_MAX))) {
			data_ = nullptr;
			size_ = 0;
		} else {
			data_ = reinterpret_cast<T*>(Allocator::allocate_zero(bytes));
			size_ = static_cast<uint32_t>(n);
		}
	}

	// A `safe_allocation` can't be copied, because it is not a proper value
	// type and it doesn't assume that the elements of the allocation can be
	// copied.
	safe_allocation(safe_allocation const&) = delete;
	safe_allocation& operator=(safe_allocation const&) = delete;

	// Moves the ownership of an allocation from one `safe_allocation` to
	// another one.
	//
	// After this operation, the moved-from `safe_allocation` is null, and
	// any iterator into the moved-from `safe_allocation` are now tied to
	// the `safe_allocation` that's the target of the assignment, in the
	// sense that the iterators will be invalidated when the target of the
	// assignment goes out of scope, not when the moved-from allocation
	// goes out of scope.
	safe_allocation(safe_allocation&& other) noexcept : data_(other.data_), size_(other.size_)
	{
		other.data_ = nullptr;
		other.size_ = 0;
	}

	// Clears a `safe_allocation`, making it a null allocation.
	//
	// If the `safe_allocation` was pointing to valid memory, the objects
	// in that memory are destroyed and that memory is freed.
	safe_allocation&
	operator=(sa_detail::nullptr_t)
	{
		if (data_ != nullptr) {
			destroy_dealloc_(data_, size_);
		}
		data_ = nullptr;
		size_ = 0;
		return *this;
	}

	// Moves the ownership of an allocation from one `safe_allocation` to
	// another one.
	//
	// After this operation, the moved-from `safe_allocation` is null, and
	// any iterator to the moved-from `safe_allocation` obtained before the
	// move operation are invalidated.
	//
	// If the destination `safe_allocation` was pointing to memory before the
	// move-assignment, the objects in that memory are destroyed and the
	// memory itself is freed.
	//
	// In case of self-move-assignment, nothing is done.
	safe_allocation&
	operator=(safe_allocation&& other)
	{
		if (&other == this) {
			return *this;
		}

		T* old_data = data_;
		size_t old_size = size_;

		data_ = other.data_;
		size_ = other.size_;
		other.data_ = nullptr;
		other.size_ = 0;

		if (old_data != nullptr) {
			destroy_dealloc_(old_data, old_size);
		}

		return *this;
	}

	// Destroys a `safe_allocation`, destroying the objects in it and
	// deallocating the underlying memory with the `Allocator`.
	//
	// If the `safe_allocation` is null, this destructor does nothing.
	~safe_allocation()
	{
		if (data_ != nullptr) {
			destroy_dealloc_(data_, size_);
		}
	}

	// Returns whether a `safe_allocation` is non-null, i.e. whether it is
	// pointing to some memory.
	explicit
	operator bool() const noexcept
	{
		return data_ != nullptr;
	}

	using iterator = bounded_ptr<T, TrappingPolicy>;
	using const_iterator = bounded_ptr<T const, TrappingPolicy>;

	// The following methods allow obtaining iterators (i.e. cursors) to
	// objects inside a `safe_allocation`.
	//
	// The iterators of a `safe_allocation` are `bounded_ptr`s, which know
	// the bounds of the allocation and will trap when dereferenced outside
	// of those bounds.
	//
	// `begin()` returns a (const) iterator to the first element in the
	// allocation, and `end()` returns a (const) iterator to one-past-the-last
	// element in the allocation. The `end()` iterator can't be dereferenced,
	// since it is out of bounds.
	//
	// If the allocation is null, these methods will return null `bounded_ptr`s,
	// which can be checked for equality but can't be dereferenced.
	OS_ALWAYS_INLINE iterator
	begin() noexcept
	{
		if (data_ == nullptr) {
			return iterator();
		} else {
			return iterator(data_, data_, data_ + size_);
		}
	}
	OS_ALWAYS_INLINE const_iterator
	begin() const noexcept
	{
		if (data_ == nullptr) {
			return const_iterator();
		} else {
			return const_iterator(data_, data_, data_ + size_);
		}
	}
	iterator
	end() noexcept
	{
		if (data_ == nullptr) {
			return iterator();
		} else {
			return iterator(data_ + size_, data_, data_ + size_);
		}
	}
	const_iterator
	end() const noexcept
	{
		if (data_ == nullptr) {
			return const_iterator();
		} else {
			return const_iterator(data_ + size_, data_, data_ + size_);
		}
	}

	// Returns the number of objects in the allocation.
	//
	// This method returns `0` if the allocation is null, since such an
	// allocation behaves the same as an empty range.
	size_t
	size() const
	{
		return size_;
	}

	// Returns a non-owning pointer to the underlying memory managed by a
	// `safe_allocation`.
	//
	// This method can be called even if the `safe_allocation` is null, in
	// which case the returned pointer will be null.
	T*
	data() noexcept
	{
		return data_;
	}
	T const*
	data() const noexcept
	{
		return data_;
	}

	// Access the n-th element of an allocation.
	//
	// If `n` is out of the bounds of the allocation, this operation will
	// trap. If the allocation is null, this operation will trap too.
	//
	// Design note:
	// We voluntarily use a signed type to represent the index even though a
	// negative index will always cause a trap. If we used an unsigned type,
	// we could get an implicit conversion from signed to unsigned, which
	// could silently wrap around. We think trapping early is more likely
	// to be helpful in this situation.
	OS_ALWAYS_INLINE T&
	operator[](ptrdiff_t n)
	{
		return begin()[n]; // trap happens in `bounded_ptr` if null or OOB
	}
	OS_ALWAYS_INLINE T const&
	operator[](ptrdiff_t n) const
	{
		return begin()[n]; // trap happens in `bounded_ptr` if null or OOB
	}

private:
	// Swap support
	friend void
	swap(safe_allocation& a, safe_allocation& b) noexcept
	{
		sa_detail::generic_swap(a.data_, b.data_);
		sa_detail::generic_swap(a.size_, b.size_);
	}

	static void
	destroy_dealloc_(T* ptr, size_t size)
	{
		sa_detail::destroy(ptr, ptr + size);
		// `size * sizeof(T)` can't overflow, because it would have
		// overflowed when the allocation was performed otherwise.
		using RawT = sa_detail::remove_const_t<T>;
		Allocator::deallocate(const_cast<RawT*>(ptr), size * sizeof(T));
	}

	T* data_;
	uint32_t size_;
};

// The comparison functions against `nullptr` all return whether the allocation
// is null or not.
template <typename T, typename A, typename P>
bool
operator==(safe_allocation<T, A, P> const& x, sa_detail::nullptr_t)
{
	return !static_cast<bool>(x);
}

template <typename T, typename A, typename P>
bool
operator!=(safe_allocation<T, A, P> const& x, sa_detail::nullptr_t)
{
	return !(x == nullptr);
}

template <typename T, typename A, typename P>
bool
operator==(sa_detail::nullptr_t, safe_allocation<T, A, P> const& x)
{
	return x == nullptr;
}

template <typename T, typename A, typename P>
bool
operator!=(sa_detail::nullptr_t, safe_allocation<T, A, P> const& x)
{
	return !(x == nullptr);
}
} // end namespace libkern

#endif /* !TAPI */

#endif // !XNU_LIBKERN_LIBKERN_CXX_SAFE_ALLOCATION_H