Commit 9f955e5f authored by Yuki Shiino's avatar Yuki Shiino Committed by Commit Bot

base/memory: Support implicit upcast of CheckedPtr<T>

base::CheckedPtr<T> should behave as if it's a raw pointer.
When an implicit conversion from U* to T* is allowed, similarly
an implicit conversion from CheckedPtr<U> to CheckedPtr<T>
should be allowed, too.  This patch supports such an upcast of
CheckedPtr<T>.

Bug: 1073933
Change-Id: I0b93c95f44cf27849a96ea14c2025fd3f4a5b7e9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2303409Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarBartek Nowierski <bartekn@chromium.org>
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790796}
parent 79758e7c
...@@ -81,6 +81,15 @@ struct CheckedPtrNoOpImpl { ...@@ -81,6 +81,15 @@ struct CheckedPtrNoOpImpl {
return reinterpret_cast<void*>(wrapped_ptr); return reinterpret_cast<void*>(wrapped_ptr);
} }
// Upcasts the wrapped pointer.
template <typename To, typename From>
static ALWAYS_INLINE constexpr uintptr_t Upcast(uintptr_t wrapped_ptr) {
static_assert(std::is_convertible<From*, To*>::value,
"From must be convertible to To.");
return reinterpret_cast<uintptr_t>(
static_cast<To*>(reinterpret_cast<From*>(wrapped_ptr)));
}
// Advance the wrapped pointer by |delta| bytes. // Advance the wrapped pointer by |delta| bytes.
static ALWAYS_INLINE uintptr_t Advance(uintptr_t wrapped_ptr, size_t delta) { static ALWAYS_INLINE uintptr_t Advance(uintptr_t wrapped_ptr, size_t delta) {
return wrapped_ptr + delta; return wrapped_ptr + delta;
...@@ -315,6 +324,36 @@ struct CheckedPtr2OrMTEImpl { ...@@ -315,6 +324,36 @@ struct CheckedPtr2OrMTEImpl {
return ExtractPtr(wrapped_ptr); return ExtractPtr(wrapped_ptr);
} }
// Upcasts the wrapped pointer.
template <typename To, typename From>
static ALWAYS_INLINE uintptr_t Upcast(uintptr_t wrapped_ptr) {
static_assert(std::is_convertible<From*, To*>::value,
"From must be convertible to To.");
#if ENABLE_TAG_FOR_CHECKED_PTR2 || ENABLE_TAG_FOR_SINGLE_TAG_CHECKED_PTR
if (IsPtrUnaffectedByUpcast<To, From>())
return wrapped_ptr;
// CheckedPtr2 doesn't support a pointer pointing in the middle of an
// allocated object, so disable the generation tag.
//
// Clearing tag is not needed for ENABLE_TAG_FOR_SINGLE_TAG_CHECKED_PTR,
// but do it anyway for apples-to-apples comparison with
// ENABLE_TAG_FOR_CHECKED_PTR2.
uintptr_t base_addr = reinterpret_cast<uintptr_t>(
static_cast<To*>(reinterpret_cast<From*>(ExtractPtr(wrapped_ptr))));
return base_addr;
#elif ENABLE_TAG_FOR_MTE_CHECKED_PTR
// The top-bit generation tag must not affect the result of upcast.
return reinterpret_cast<uintptr_t>(
static_cast<To*>(reinterpret_cast<From*>(wrapped_ptr)));
#else
static_assert(std::is_void<To>::value, // Always false.
"Unknown tagging mode");
return 0;
#endif
}
// Advance the wrapped pointer by |delta| bytes. // Advance the wrapped pointer by |delta| bytes.
static ALWAYS_INLINE uintptr_t Advance(uintptr_t wrapped_ptr, size_t delta) { static ALWAYS_INLINE uintptr_t Advance(uintptr_t wrapped_ptr, size_t delta) {
// Mask out the generation to disable the protection. It's not supported for // Mask out the generation to disable the protection. It's not supported for
...@@ -336,6 +375,17 @@ struct CheckedPtr2OrMTEImpl { ...@@ -336,6 +375,17 @@ struct CheckedPtr2OrMTEImpl {
return wrapped_ptr & kGenerationMask; return wrapped_ptr & kGenerationMask;
} }
template <typename To, typename From>
static constexpr ALWAYS_INLINE bool IsPtrUnaffectedByUpcast() {
static_assert(std::is_convertible<From*, To*>::value,
"From must be convertible to To.");
uintptr_t d = 0x10000;
From* dp = reinterpret_cast<From*>(d);
To* bp = dp;
uintptr_t b = reinterpret_cast<uintptr_t>(bp);
return b == d;
}
// This relies on nullptr and 0 being equal in the eyes of reinterpret_cast, // This relies on nullptr and 0 being equal in the eyes of reinterpret_cast,
// which apparently isn't true in some rare environments. // which apparently isn't true in some rare environments.
static constexpr uintptr_t kWrappedNullPtr = 0; static constexpr uintptr_t kWrappedNullPtr = 0;
...@@ -385,6 +435,23 @@ class CheckedPtr { ...@@ -385,6 +435,23 @@ class CheckedPtr {
// NOLINTNEXTLINE(runtime/explicit) // NOLINTNEXTLINE(runtime/explicit)
ALWAYS_INLINE CheckedPtr(T* p) noexcept : wrapped_ptr_(Impl::WrapRawPtr(p)) {} ALWAYS_INLINE CheckedPtr(T* p) noexcept : wrapped_ptr_(Impl::WrapRawPtr(p)) {}
// Deliberately implicit in order to support implicit upcast.
template <typename U,
typename Unused = std::enable_if_t<
std::is_convertible<U*, T*>::value &&
!std::is_void<typename std::remove_cv<T>::type>::value>>
// NOLINTNEXTLINE(google-explicit-constructor)
ALWAYS_INLINE CheckedPtr(const CheckedPtr<U, Impl>& ptr) noexcept
: wrapped_ptr_(Impl::template Upcast<T, U>(ptr.wrapped_ptr_)) {}
// Deliberately implicit in order to support implicit upcast.
template <typename U,
typename Unused = std::enable_if_t<
std::is_convertible<U*, T*>::value &&
!std::is_void<typename std::remove_cv<T>::type>::value>>
// NOLINTNEXTLINE(google-explicit-constructor)
ALWAYS_INLINE CheckedPtr(CheckedPtr<U, Impl>&& ptr) noexcept
: wrapped_ptr_(Impl::template Upcast<T, U>(ptr.wrapped_ptr_)) {}
// In addition to nullptr_t ctor above, CheckedPtr needs to have these // In addition to nullptr_t ctor above, CheckedPtr needs to have these
// as |=default| or |constexpr| to avoid hitting -Wglobal-constructors in // as |=default| or |constexpr| to avoid hitting -Wglobal-constructors in
// cases like this: // cases like this:
...@@ -395,12 +462,30 @@ class CheckedPtr { ...@@ -395,12 +462,30 @@ class CheckedPtr {
CheckedPtr& operator=(const CheckedPtr&) noexcept = default; CheckedPtr& operator=(const CheckedPtr&) noexcept = default;
CheckedPtr& operator=(CheckedPtr&&) noexcept = default; CheckedPtr& operator=(CheckedPtr&&) noexcept = default;
ALWAYS_INLINE CheckedPtr& operator=(std::nullptr_t) noexcept {
wrapped_ptr_ = Impl::GetWrappedNullPtr();
return *this;
}
ALWAYS_INLINE CheckedPtr& operator=(T* p) noexcept { ALWAYS_INLINE CheckedPtr& operator=(T* p) noexcept {
wrapped_ptr_ = Impl::WrapRawPtr(p); wrapped_ptr_ = Impl::WrapRawPtr(p);
return *this; return *this;
} }
ALWAYS_INLINE CheckedPtr& operator=(std::nullptr_t) noexcept {
wrapped_ptr_ = Impl::GetWrappedNullPtr(); // Upcast assignment
template <typename U,
typename Unused = std::enable_if_t<
std::is_convertible<U*, T*>::value &&
!std::is_void<typename std::remove_cv<T>::type>::value>>
ALWAYS_INLINE CheckedPtr& operator=(const CheckedPtr<U, Impl>& ptr) noexcept {
wrapped_ptr_ = Impl::template Upcast<T, U>(ptr.wrapped_ptr_);
return *this;
}
template <typename U,
typename Unused = std::enable_if_t<
std::is_convertible<U*, T*>::value &&
!std::is_void<typename std::remove_cv<T>::type>::value>>
ALWAYS_INLINE CheckedPtr& operator=(CheckedPtr<U, Impl>&& ptr) noexcept {
wrapped_ptr_ = Impl::template Upcast<T, U>(ptr.wrapped_ptr_);
return *this; return *this;
} }
......
...@@ -513,6 +513,86 @@ TEST_F(CheckedPtrTest, Cast) { ...@@ -513,6 +513,86 @@ TEST_F(CheckedPtrTest, Cast) {
EXPECT_EQ(checked_derived_ptr4->d, 1024); EXPECT_EQ(checked_derived_ptr4->d, 1024);
} }
TEST_F(CheckedPtrTest, UpcastConvertible) {
{
Derived derived_val(42, 84, 1024);
CheckedPtr<Derived> checked_derived_ptr = &derived_val;
CheckedPtr<Base1> checked_base1_ptr(checked_derived_ptr);
EXPECT_EQ(checked_base1_ptr->b1, 42);
CheckedPtr<Base2> checked_base2_ptr(checked_derived_ptr);
EXPECT_EQ(checked_base2_ptr->b2, 84);
checked_base1_ptr = checked_derived_ptr;
EXPECT_EQ(checked_base1_ptr->b1, 42);
checked_base2_ptr = checked_derived_ptr;
EXPECT_EQ(checked_base2_ptr->b2, 84);
EXPECT_EQ(checked_base1_ptr, checked_derived_ptr);
EXPECT_EQ(checked_base2_ptr, checked_derived_ptr);
}
{
Derived derived_val(42, 84, 1024);
CheckedPtr<Derived> checked_derived_ptr = &derived_val;
CheckedPtr<Base1> checked_base1_ptr(std::move(checked_derived_ptr));
EXPECT_EQ(checked_base1_ptr->b1, 42);
CheckedPtr<Base2> checked_base2_ptr(std::move(checked_derived_ptr));
EXPECT_EQ(checked_base2_ptr->b2, 84);
checked_base1_ptr = std::move(checked_derived_ptr);
EXPECT_EQ(checked_base1_ptr->b1, 42);
checked_base2_ptr = std::move(checked_derived_ptr);
EXPECT_EQ(checked_base2_ptr->b2, 84);
EXPECT_EQ(checked_base1_ptr, checked_derived_ptr);
EXPECT_EQ(checked_base2_ptr, checked_derived_ptr);
}
}
TEST_F(CheckedPtrTest, UpcastNotConvertible) {
class Base {};
class Derived : private Base {};
class Unrelated {};
EXPECT_FALSE(
(std::is_convertible<CheckedPtr<Derived>, CheckedPtr<Base>>::value));
EXPECT_FALSE(
(std::is_convertible<CheckedPtr<Unrelated>, CheckedPtr<Base>>::value));
EXPECT_FALSE(
(std::is_convertible<CheckedPtr<Unrelated>, CheckedPtr<void>>::value));
EXPECT_FALSE(
(std::is_convertible<CheckedPtr<void>, CheckedPtr<Unrelated>>::value));
EXPECT_FALSE(
(std::is_convertible<CheckedPtr<int64_t>, CheckedPtr<int32_t>>::value));
EXPECT_FALSE(
(std::is_convertible<CheckedPtr<int16_t>, CheckedPtr<int32_t>>::value));
}
TEST_F(CheckedPtrTest, UpcastPerformance) {
{
Derived derived_val(42, 84, 1024);
CountingCheckedPtr<Derived> checked_derived_ptr = &derived_val;
CountingCheckedPtr<Base1> checked_base1_ptr(checked_derived_ptr);
CountingCheckedPtr<Base2> checked_base2_ptr(checked_derived_ptr);
checked_base1_ptr = checked_derived_ptr;
checked_base2_ptr = checked_derived_ptr;
}
{
Derived derived_val(42, 84, 1024);
CountingCheckedPtr<Derived> checked_derived_ptr = &derived_val;
CountingCheckedPtr<Base1> checked_base1_ptr(std::move(checked_derived_ptr));
CountingCheckedPtr<Base2> checked_base2_ptr(std::move(checked_derived_ptr));
checked_base1_ptr = std::move(checked_derived_ptr);
checked_base2_ptr = std::move(checked_derived_ptr);
}
EXPECT_EQ(g_get_for_comparison_cnt, 0);
EXPECT_EQ(g_get_for_extraction_cnt, 0);
EXPECT_EQ(g_get_for_dereference_cnt, 0);
}
TEST_F(CheckedPtrTest, CustomSwap) { TEST_F(CheckedPtrTest, CustomSwap) {
int foo1, foo2; int foo1, foo2;
CountingCheckedPtr<int> ptr1(&foo1); CountingCheckedPtr<int> ptr1(&foo1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment