struct __attribute__()

in libkern/libkern/c++/bounded_ptr.h [143:497]


struct __attribute__((trivial_abi)) bounded_ptr {
private:
	using CharType = detail::copy_cv_t<T, char>;

public:
	// Creates a null `bounded_ptr`.
	//
	// A null `bounded_ptr` does not point to any object and is conceptually
	// out of bounds, so dereferencing it will trap. "Observing" operations
	// like comparison and check-for-null, along with assignment, are valid
	// operations on a null `bounded_ptr`.
	OS_ALWAYS_INLINE constexpr
	bounded_ptr(detail::nullptr_t)
		: base_(nullptr), count_(0), offset_(0)
	{
	}

	OS_ALWAYS_INLINE constexpr
	explicit
	bounded_ptr()
		: bounded_ptr(nullptr)
	{
	}

	// Creates a `bounded_ptr` pointing to the given object, and whose bounds
	// are described by the provided `[begin, end)` range.
	//
	// This constructor does not check whether the constructed pointer is
	// within its bounds. However, it does check that the provided `[begin, end)`
	// range is a valid range (that is, `begin <= end`).
	//
	// Furthermore, the number of bytes in the range of in-bounds memory must be
	// representable by a uint32_t, which means that there can be no more than
	// 2^32 bytes (i.e. 4GB) in that range. Otherwise, the constructor will trap.
	OS_ALWAYS_INLINE explicit
	bounded_ptr(T* pointer, T const* begin, T const* end)
	{
		base_ = reinterpret_cast<CharType*>(const_cast<T*>(begin));

		// Store (end - begin) into count_, making sure we don't overflow
		if (__improbable(os_sub_overflow(reinterpret_cast<uintptr_t>(end),
		    reinterpret_cast<uintptr_t>(begin),
		    &count_))) {
			TrappingPolicy::trap("The range of valid memory is too large to be represented "
			    "by this type, or [begin, end) is not a well-formed range");
		}

		// Store (pointer - begin) into offset_, making sure we don't overflow.
		// Note that offset_ can be negative if `pointer` is outside of the
		// range delimited by [begin, end), which can be valid if it represents
		// e.g. a subrange of an array.
		if (__improbable(os_sub_overflow(reinterpret_cast<uintptr_t>(pointer),
		    reinterpret_cast<uintptr_t>(begin),
		    &offset_))) {
			TrappingPolicy::trap("The offset of the pointer inside its valid memory "
			    "range can't be represented using int32_t");
		}
	}

	// Creates a `bounded_ptr` to a type `T` from a `bounded_ptr` to a type `U`.
	//
	// This converting constructor is enabled whenever `U*` is implicitly
	// convertible to `T*`. This allows the usual implicit conversions
	// between base-and-derived types, and also from any type `U*` to a
	// `void*`. If other casts (like between unrelated pointer types) are
	// desired, `libkern::reinterpret_pointer_cast` can be used instead.
	//
	// The bounds on the resulting `bounded_ptr` are inherited from the
	// original `bounded_ptr`.
	template <typename U, typename Policy, typename = detail::enable_if_t<detail::is_convertible_v<U*, T*> > >
	OS_ALWAYS_INLINE
	bounded_ptr(bounded_ptr<U, Policy> const & other)
		: base_(other.base_)
		, count_(other.count_)
		, offset_(static_cast<int32_t>(reinterpret_cast<CharType*>(static_cast<T*>(other.get_ptr_())) - other.base_))
	{
	}

	// Assigns a `bounded_ptr` to a type `U` to a `bounded_ptr` to a type `T`,
	// as long as `U*` is convertible to `T*`.
	//
	// This is a rebinding operation, like assignment between raw pointers,
	// and the destination `bounded_ptr` will inherit the bounds of the
	// source `bounded_ptr`.
	template <typename U, typename Policy, typename = detail::enable_if_t<detail::is_convertible_v<U*, T*> > >
	OS_ALWAYS_INLINE bounded_ptr&
	operator=(bounded_ptr<U, Policy> const& other)
	{
		base_ = other.base_;
		count_ = other.count_;
		offset_ = static_cast<int32_t>(reinterpret_cast<CharType*>(static_cast<T*>(other.get_ptr_())) - other.base_);
		return *this;
	}

	// Sets a `bounded_ptr` to null.
	//
	// This is effectively equivalent to assigning a default-constructed
	// `bounded_ptr` to the target. As a result, the original bounds of
	// the `bounded_ptr` are discarded, and the resulting `bounded_ptr`
	// is both out-of-bounds and also has no bounds assigned to it (like
	// a default-constructed `bounded_ptr`).
	OS_ALWAYS_INLINE bounded_ptr&
	operator=(detail::nullptr_t)
	{
		*this = bounded_ptr();
		return *this;
	}

	// Returns a reference to the object pointed-to by the `bounded_ptr`.
	//
	// Traps if the pointer is pointing outside of its bounds.
	//
	// Also note that this function will trap when dereferencing a null
	// `bounded_ptr`, unless the bounds of the pointer have been set and
	// include address 0, in which case there's effectively nothing to
	// diagnose.
	template <typename T_ = T> // delay instantiation to avoid forming invalid ref for bounded_ptr<void>
	OS_ALWAYS_INLINE T_&
	operator*() const
	{
		if (__improbable(!in_bounds_())) {
			TrappingPolicy::trap("bounded_ptr<T>::operator*: Dereferencing this pointer "
			    "would access memory outside of the bounds set originally");
		}
		return *get_ptr_();
	}

	OS_ALWAYS_INLINE T*
	operator->() const
	{
		if (__improbable(!in_bounds_())) {
			TrappingPolicy::trap("bounded_ptr<T>::operator->: Accessing a member through this pointer "
			    "would access memory outside of the bounds set originally");
		}
		return get_ptr_();
	}

	// Provides access to the n-th element past the given pointer.
	//
	// The `bounded_ptr` validates whether the provided index is within the
	// bounds of the `bounded_ptr`. Like for raw pointers, a negative index
	// may be passed, in which case the pointer is accessed at a negative
	// offset (which must still be in bounds).
	template <typename T_ = T> // delay instantiation to avoid forming invalid ref for bounded_ptr<void>
	OS_ALWAYS_INLINE T_&
	operator[](ptrdiff_t n) const
	{
		return *(*this + n);
	}

	// Converts a `bounded_ptr` to a raw pointer, after checking it is within
	// its bounds.
	//
	// The primary intended usage of this function is to aid bridging between
	// code that uses `bounded_ptr`s and code that does not.
	OS_ALWAYS_INLINE T*
	discard_bounds() const
	{
		if (__improbable(!in_bounds_())) {
			TrappingPolicy::trap("bounded_ptr<T>::discard_bounds: Discarding the bounds on "
			    "this pointer would lose the fact that it is outside of the "
			    "bounds set originally");
		}
		return get_ptr_();
	}

	// Converts a `bounded_ptr` to a raw pointer, without checking whether the
	// pointer is within its bounds.
	//
	// Like `discard_bounds()`, the primary intended usage of this function
	// is to aid bridging between code that uses `bounded_ptr`s and code that
	// does not. However, unlike `discard_bounds()`, this function does not
	// validate that the returned pointer is in bounds. This functionality is
	// necessary when the pointer represents something that can't be
	// dereferenced (hence it's OK for it to be out-of-bounds), but that
	// is still useful for other purposes like comparing against other
	// pointers. An example of that is the `end` pointer in a half-open
	// interval `[begin, end)`, where the `end` pointer is out-of-bounds and
	// can't be dereferenced, yet it's still useful to delimit the range.
	OS_ALWAYS_INLINE T*
	unsafe_discard_bounds() const
	{
		return get_ptr_();
	}

	// Implicit conversion to bool, returning whether the pointer is null.
	//
	// This operation does not perform any validation of the bounds.
	OS_ALWAYS_INLINE explicit
	operator bool() const
	{
		return get_ptr_() != nullptr;
	}

	// Increment/decrement a `bounded_ptr`.
	//
	// Like for other arithmetic operations, this does not check whether the
	// increment or decrement operation results in an out-of-bounds pointer.
	OS_ALWAYS_INLINE bounded_ptr&
	operator++()
	{
		*this += 1;
		return *this;
	}
	OS_ALWAYS_INLINE bounded_ptr
	operator++(int)
	{
		bounded_ptr old = *this;
		++*this;
		return old;
	}
	OS_ALWAYS_INLINE bounded_ptr&
	operator--()
	{
		*this -= 1;
		return *this;
	}
	OS_ALWAYS_INLINE bounded_ptr
	operator--(int)
	{
		bounded_ptr old = *this;
		--*this;
		return old;
	}

	// Increment or decrement a `bounded_ptr` by a given offset.
	//
	// This is equivalent to adding the given offset to the underlying raw
	// pointer. In particular, the bounds of the `bounded_ptr` are left
	// untouched by this operation. Furthermore, like for raw pointers, it
	// is possible to provide a negative offset, which will have the effect
	// of decrementing the `bounded_ptr` instead of incrementing it.
	//
	// Also note that the offset is NOT a number of bytes -- just like for
	// raw pointers, it is a number of "positions" to move the pointer from,
	// which essentially means `n * sizeof(T)` bytes. Again, this works exactly
	// the same as a raw pointer to an object of type `T`.
	//
	// Like other arithmetic operations, this does not check whether the
	// increment or decrement operation results in an out-of-bounds pointer.
	// However, this does check whether the arithmetic operation would result
	// in an overflow, in which case the operation will trap.
	template <typename T_ = T>
	OS_ALWAYS_INLINE bounded_ptr&
	operator+=(ptrdiff_t n)
	{
		static_assert(!detail::is_void_v<T_>, "Arithmetic on bounded_ptr<void> is not allowed.");

		ptrdiff_t bytes;
		if (__improbable(os_mul_overflow(n, sizeof(T), &bytes))) {
			TrappingPolicy::trap(
				"bounded_ptr<T>::operator+=(n): Calculating the number of bytes to "
				"add to the offset (n * sizeof(T)) would trigger an overflow");
		}
		if (__improbable(os_add_overflow(offset_, bytes, &offset_))) {
			TrappingPolicy::trap(
				"bounded_ptr<T>::operator+=(n): Adding the specified number of bytes "
				"to the offset representing the current position would overflow.");
		}
		return *this;
	}

	template <typename T_ = T>
	OS_ALWAYS_INLINE bounded_ptr&
	operator-=(ptrdiff_t n)
	{
		static_assert(!detail::is_void_v<T_>, "Arithmetic on bounded_ptr<void> is not allowed.");

		ptrdiff_t bytes;
		if (__improbable(os_mul_overflow(n, sizeof(T), &bytes))) {
			TrappingPolicy::trap(
				"bounded_ptr<T>::operator-=(n): Calculating the number of bytes to "
				"subtract from the offset (n * sizeof(T)) would trigger an overflow");
		}
		if (__improbable(os_sub_overflow(offset_, bytes, &offset_))) {
			TrappingPolicy::trap(
				"bounded_ptr<T>::operator-=(n): Subtracting the specified number of bytes "
				"from the offset representing the current position would overflow.");
		}
		return *this;
	}

	friend OS_ALWAYS_INLINE bounded_ptr
	operator+(bounded_ptr p, ptrdiff_t n)
	{
		p += n;
		return p;
	}
	friend OS_ALWAYS_INLINE bounded_ptr
	operator+(ptrdiff_t n, bounded_ptr p)
	{
		p += n;
		return p;
	}
	friend OS_ALWAYS_INLINE bounded_ptr
	operator-(bounded_ptr p, ptrdiff_t n)
	{
		p -= n;
		return p;
	}

	// Returns the difference between two `bounded_ptr`s.
	//
	// This is semantically equivalent to subtracting the two underlying
	// pointers. The bounds of the pointers are not validated by this
	// operation.
	friend OS_ALWAYS_INLINE ptrdiff_t
	operator-(bounded_ptr const& a, bounded_ptr const& b)
	{
		return a.get_ptr_() - b.get_ptr_();
	}

	friend OS_ALWAYS_INLINE ptrdiff_t
	operator-(bounded_ptr const& a, T const* b)
	{
		return a.get_ptr_() - b;
	}

	friend OS_ALWAYS_INLINE ptrdiff_t
	operator-(T const* a, bounded_ptr const& b)
	{
		return a - b.get_ptr_();
	}

private:
	OS_ALWAYS_INLINE bool
	in_bounds_() const
	{
		static_assert(detail::sizeof_v<T> <= UINT32_MAX - INT32_MAX,
		    "The type pointed-to by bounded_ptr is too large, which would defeat "
		    "our optimization to check for inboundedness using arithmetic on unsigned");
		return offset_ >= 0 && static_cast<uint32_t>(offset_) + static_cast<uint32_t>(detail::sizeof_v<T>) <= count_;
	}

	OS_ALWAYS_INLINE T*
	get_ptr_() const
	{
		// Compute `base_ + offset_`, catching overflows.
		uintptr_t ptr;
		if (__improbable(os_add_overflow(reinterpret_cast<uintptr_t>(base_), offset_, &ptr))) {
			TrappingPolicy::trap("This bounded_ptr is pointing to memory outside of what can "
			    "be represented by a native pointer.");
		}
		return reinterpret_cast<T*>(ptr);
	}

	template <typename T_, typename U, typename Policy>
	friend bounded_ptr<T_, Policy> reinterpret_pointer_cast(bounded_ptr<U, Policy> const&) noexcept;

	template <typename U, typename P> friend struct bounded_ptr; // for cross-type operations and conversions

	CharType* base_; // pointer to the beginning of the valid address range
	uint32_t count_; // number of bytes considered in-bounds (non-negative)
	int32_t offset_; // current offset into the range, in bytes
};