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
};