blob: 4e77b7584a1aa0dca67d64529ca54a1b1d9f225d [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_FIDL_LLCPP_TRACKING_PTR_H_
#define LIB_FIDL_LLCPP_TRACKING_PTR_H_
#include <lib/fidl/walker.h>
#include <cstddef>
#include <functional>
#include <memory>
#include <type_traits>
#include "aligned.h"
#include "array.h"
#include "unowned_ptr.h"
namespace fidl {
// tracking_ptr is a pointer that tracks ownership - it can either own or not own the pointed
// memory.
//
// When it owns memory, it acts similar to unique_ptr. When the pointer goes out of scope, the
// pointed object is deleted. tracking_ptr only supports move constructors like unique_ptr.
// When tracking_ptr points to unowned memory, no deletion occurs when tracking_ptr goes out
// of scope.
//
// This is implemented by reserving the least significant bit (LSB) of the pointer for use by
// tracking_ptr. For this to work, pointed objects must have at least 2-byte alignment so
// that the LSB of the pointer is 0. Heap allocated objects on modern systems are at least
// 4-byte aligned (32-bit) or 8-byte aligned (64-bit). An LSB of 0 means the pointed value
// is unowned. If the bit is 1, the pointed value is owned by tracking_ptr and will be freed
// when tracking_ptr is destructed.
//
// Arrays are special cased, similar to unique_ptr. tracking_ptr<int[]> does not act like a
// int[]* but rather it acts like a int* with features specific to arrays, such as indexing
// and deletion via the delete[] operator. The array specializations of tracking_ptr --
// e.g. tracking_ptr<int[]> -- has an internal memory layout that is a pointer + bool, so it
// is NOT the same width as a raw pointer. The LSB is not used for ownership for arrays
// because it is common to read from a buffer starting at arbitrary offset.
//
// tracking_ptr<void> is also a special case and generally should only be used when it is
// necessary to store values in an untyped representation (for instance if a pointer can be
// one of a few types). tracking_ptr<void> can only be constructed with a non-null value
// from another tracking_ptr. It is an error to destruct a tracking_ptr<void> containing an
// owned pointer - it is expected that the pointer is moved out of the tracking_ptr first.
//
// Example:
// int i = 1;
// tracking_ptr<int> ptr = unowned_ptr_t<int>(&i); // Unowned pointer.
// ptr = std::make_unique<int>(2); // Owned pointer.
//
// tracking_ptr<int[]> array_ptr = std::make_unique<int[]>(2);
// array_ptr[1] = 5;
//
// Note: despite Fuchsia disabling exceptions, some methods are marked noexcept to allow
// for optimizations. For instance, vector has an optimization where it will move rather
// than copy if certain methods are marked with noexcept.
template <typename T>
class tracking_ptr final {
template <typename>
friend class tracking_ptr;
// A marked_ptr is a pointer with the LSB reserved for the ownership bit.
using marked_ptr = uintptr_t;
static constexpr marked_ptr kOwnershipMask = internal::kNonArrayTrackingPtrOwnershipMask;
static constexpr marked_ptr kNullMarkedPtr = 0x0;
static constexpr size_t kMinAlignment = 2;
static_assert(kOwnershipMask == 1, "alignment is dependent on ownership mask");
public:
constexpr tracking_ptr() noexcept { set_marked(kNullMarkedPtr); }
constexpr tracking_ptr(std::nullptr_t) noexcept { set_marked(kNullMarkedPtr); }
// Disabled constructor that exists to produce helpful error messages for the user.
// Use templating to only trigger the static assert when the constructor is used.
template <bool U = true, typename = std::enable_if_t<U>>
tracking_ptr(T* raw_ptr) {
static_assert(!U,
"fidl::tracking_ptr cannot be constructed directly from a raw pointer. "
"If tracking_ptr should not own the memory, indicate this by constructing a "
"fidl::unowned_ptr_t "
"using the fidl::unowned_ptr(&val) helper. "
"As an alternative, consider using a fidl::Allocator."
" For heap allocator values, construct with unique_ptr<T>.");
}
template <typename U>
tracking_ptr(tracking_ptr<U>&& other) noexcept {
// Force a static cast to restrict the types of assignments that can be made.
// Ideally this would be implemented with a type trait, but none exist now.
set_marked(reinterpret_cast<marked_ptr>(
static_cast<T*>(reinterpret_cast<U*>(other.release_marked_ptr()))));
}
template <typename U = T, typename = std::enable_if_t<std::is_const<U>::value>>
tracking_ptr(tracking_ptr<std::remove_const_t<U>>&& other) noexcept {
// Force a static cast to restrict the types of assignments that can be made.
// Ideally this would be implemented with a type trait, but none exist now.
set_marked(reinterpret_cast<marked_ptr>(
static_cast<T*>(reinterpret_cast<U*>(other.release_marked_ptr()))));
}
template <typename U = T, typename = std::enable_if_t<!std::is_void<U>::value>>
tracking_ptr(std::unique_ptr<U>&& other) {
set_owned(other.release());
}
tracking_ptr(unowned_ptr_t<T> other) {
static_assert(std::alignment_of<T>::value >= kMinAlignment,
"unowned_ptr_t must point to an aligned value. "
"An insufficiently aligned value can be aligned with fidl::aligned");
set_unowned(other.get());
}
template <typename U = T, typename = std::enable_if_t<std::is_const<U>::value>>
tracking_ptr(unowned_ptr_t<std::remove_const_t<U>> other) {
static_assert(std::alignment_of<T>::value >= kMinAlignment,
"unowned_ptr_t must point to an aligned value. "
"An insufficiently aligned value can be aligned with fidl::aligned");
set_unowned(other.get());
}
// This constructor exists to strip off 'aligned' from the type (aligned<bool> -> bool).
tracking_ptr(unowned_ptr_t<aligned<T>> other) { set_unowned(&other->value); }
tracking_ptr(const tracking_ptr&) = delete;
~tracking_ptr() { reset_marked(kNullMarkedPtr); }
tracking_ptr& operator=(tracking_ptr&& other) noexcept {
reset_marked(other.release_marked_ptr());
return *this;
}
tracking_ptr& operator=(const tracking_ptr&) = delete;
template <typename U = T, typename = std::enable_if_t<!std::is_void<U>::value>>
U& operator*() const {
return *get();
}
template <typename U = T, typename = std::enable_if_t<!std::is_void<U>::value>>
U* operator->() const noexcept {
return get();
}
T* get() const noexcept { return reinterpret_cast<T*>(mptr_ & ~kOwnershipMask); }
explicit operator bool() const noexcept { return get() != nullptr; }
private:
template <typename U, typename = void>
struct Deleter {
static void delete_ptr(U* ptr) { delete ptr; }
};
template <typename U>
struct Deleter<U, std::enable_if_t<std::is_void<U>::value>> {
static void delete_ptr(U*) {
assert(false &&
"Cannot delete void* in tracking_ptr<void>. "
"First std::move contained value to appropriate typed pointer.");
}
};
void reset_marked(marked_ptr new_ptr) {
if (is_owned()) {
Deleter<T>::delete_ptr(get());
}
set_marked(new_ptr);
}
bool is_owned() const noexcept { return (mptr_ & kOwnershipMask) != 0; }
void set_marked(marked_ptr new_ptr) noexcept { mptr_ = new_ptr; }
void set_unowned(T* new_ptr) {
assert_lsb_not_set(new_ptr);
set_marked(reinterpret_cast<marked_ptr>(new_ptr));
}
void set_owned(T* new_ptr) {
assert_lsb_not_set(new_ptr);
marked_ptr ptr_marked_owned = reinterpret_cast<marked_ptr>(new_ptr) | kOwnershipMask;
set_marked(ptr_marked_owned);
}
static void assert_lsb_not_set(T* p) {
if (reinterpret_cast<marked_ptr>(p) & kOwnershipMask) {
abort();
}
}
marked_ptr release_marked_ptr() noexcept {
marked_ptr temp = mptr_;
if (is_owned()) {
// Unowned pointers keep their value like raw pointers, but owned pointers should zero.
mptr_ = kNullMarkedPtr;
}
return temp;
}
marked_ptr mptr_;
};
template <typename T>
class tracking_ptr<T[]> final {
template <typename>
friend class tracking_ptr;
public:
constexpr tracking_ptr() noexcept {}
constexpr tracking_ptr(std::nullptr_t) noexcept {}
// Disabled constructor that exists to produce helpful error messages for the user.
// Use templating to only trigger the static assert when the constructor is used.
template <bool U = true, typename = std::enable_if_t<U>>
tracking_ptr(T* raw_ptr) {
static_assert(!U,
"fidl::tracking_ptr cannot be constructed directly from a raw pointer. "
"If tracking_ptr should not own the memory, indicate this by constructing a "
"fidl::unowned_ptr_t "
"using the fidl::unowned_ptr(&val) helper. "
"As an alternative, consider using a fidl::Allocator."
" For heap allocator values, construct with unique_ptr<T>.");
}
tracking_ptr(tracking_ptr&& other) noexcept {
reset(other.is_owned_, other.ptr_);
other.release();
}
template <typename U = T, typename = std::enable_if_t<std::is_const<U>::value>>
tracking_ptr(tracking_ptr<std::remove_const_t<U>[]>&& other) noexcept {
reset(other.is_owned_, other.ptr_);
other.release();
}
template <typename U = T, typename = std::enable_if_t<!std::is_void<U>::value>>
tracking_ptr(std::unique_ptr<U>&& other) {
reset(true, other.release());
}
tracking_ptr(unowned_ptr_t<T> other) { reset(false, other.get()); }
template <typename U = T, typename = std::enable_if_t<std::is_const<U>::value>>
tracking_ptr(unowned_ptr_t<std::remove_const_t<U>> other) {
reset(false, other.get());
}
tracking_ptr(const tracking_ptr&) = delete;
~tracking_ptr() { reset(false, nullptr); }
tracking_ptr& operator=(tracking_ptr&& other) noexcept {
reset(other.is_owned_, other.ptr_);
other.release();
return *this;
}
tracking_ptr& operator=(const tracking_ptr&) = delete;
T& operator[](size_t index) const { return get()[index]; }
T* get() const noexcept { return ptr_; }
explicit operator bool() const noexcept { return get() != nullptr; }
bool is_owned() { return is_owned_; }
// Hand off responsibility of ownership to the caller.
// The internal data can be retrieved through get() and is_owned() before calling release().
void release() {
if (is_owned()) {
// Unowned pointers keep their value like raw pointers, but owned pointers should zero.
ptr_ = nullptr;
is_owned_ = false;
}
}
private:
void reset(bool is_owned, T* ptr) {
if (is_owned_) {
delete[] ptr_;
}
is_owned_ = is_owned;
ptr_ = ptr;
}
T* ptr_ = nullptr;
bool is_owned_ = false;
};
// Non-array tracking_ptr (and only non-array tracking_ptr) should match the layout of raw pointers.
static_assert(sizeof(fidl::tracking_ptr<void>) == sizeof(void*),
"tracking_ptr must have the same size as a raw pointer");
static_assert(sizeof(fidl::tracking_ptr<uint8_t>) == sizeof(uint8_t*),
"tracking_ptr must have the same size as a raw pointer");
// Array tracking_ptr is wider.
static_assert(sizeof(fidl::tracking_ptr<uint8_t[]>) >= sizeof(uint8_t*),
"tracking_ptr for arrays is bigger than a raw pointer");
#define TRACKING_PTR_OPERATOR_COMPARISONS(func_name, op) \
template <typename T, typename U> \
bool func_name(const tracking_ptr<T>& p1, const tracking_ptr<U>& p2) { \
return p1.get() op p2.get(); \
}
#define TRACKING_PTR_NULLPTR_COMPARISONS(func_name, op) \
template <typename T> \
bool func_name(const tracking_ptr<T>& p1, const std::nullptr_t p2) { \
return p1.get() op nullptr; \
} \
template <typename T> \
bool func_name(const std::nullptr_t p1, const tracking_ptr<T>& p2) { \
return nullptr op p2.get(); \
}
TRACKING_PTR_OPERATOR_COMPARISONS(operator==, ==)
TRACKING_PTR_NULLPTR_COMPARISONS(operator==, ==)
TRACKING_PTR_OPERATOR_COMPARISONS(operator!=, !=)
TRACKING_PTR_NULLPTR_COMPARISONS(operator!=, !=)
TRACKING_PTR_OPERATOR_COMPARISONS(operator<, <)
TRACKING_PTR_OPERATOR_COMPARISONS(operator<=, <=)
TRACKING_PTR_OPERATOR_COMPARISONS(operator>, >)
TRACKING_PTR_OPERATOR_COMPARISONS(operator>=, >=)
} // namespace fidl
namespace std {
template <typename T>
void swap(fidl::tracking_ptr<T>& lhs, fidl::tracking_ptr<T>& rhs) noexcept {
auto temp = std::move(rhs);
rhs = std::move(lhs);
lhs = std::move(temp);
}
template <typename T>
struct hash<fidl::tracking_ptr<T>> {
size_t operator()(const fidl::tracking_ptr<T>& ptr) const {
return hash<typename std::remove_extent_t<T>*>{}(ptr.get());
}
};
} // namespace std
#endif // LIB_FIDL_LLCPP_TRACKING_PTR_H_