// Copyright 2024 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include namespace DB::tests { struct Ball { struct Asserts { size_t destructed_count = 0; }; Ball(Asserts * asserts_) { copied = 0; moved = 0; value = 0; copy_assigned = 0; move_assigned = 0; asserts = asserts_; } Ball() : Ball(nullptr) {} Ball(const Ball & ball) : copied(ball.copied + 1) , moved(ball.moved) , copy_assigned(ball.copy_assigned) , move_assigned(ball.move_assigned) , value(ball.value) {} Ball(Ball && ball) : copied(ball.copied) , moved(ball.moved + 1) , copy_assigned(ball.copy_assigned) , move_assigned(ball.move_assigned) , value(ball.value) {} Ball & operator=(const Ball & p) { copied = p.copied; moved = p.moved; copy_assigned = p.copy_assigned + 1; move_assigned = p.move_assigned; value = p.value; return *this; } Ball & operator=(Ball && p) { copied = p.copied; moved = p.moved; copy_assigned = p.copy_assigned; move_assigned = p.move_assigned + 1; value = p.value; return *this; } ~Ball() { if (asserts != nullptr) { asserts->destructed_count++; } return; } size_t copied; size_t moved; size_t copy_assigned; size_t move_assigned; int value; Asserts * asserts; }; template struct MockSharedPtr { using element_type = T; using pointer = element_type *; using reference = typename std::add_lvalue_reference::type; MockSharedPtr(T * ptr) : hold_ptr(ptr) { copied = 0; moved = 0; } MockSharedPtr(const MockSharedPtr & p) { hold_ptr = p.hold_ptr; copied = p.copied + 1; moved = p.moved; } MockSharedPtr(MockSharedPtr && p) { hold_ptr = p.hold_ptr; copied = p.copied; moved = p.moved + 1; } T & operator=(const MockSharedPtr & p) { hold_ptr = p.hold_ptr; } T & operator=(MockSharedPtr && p) { hold_ptr = p.hold_ptr; } T & operator*() const { return *hold_ptr; } T * operator->() const { return hold_ptr; } operator bool() const { return hold_ptr == nullptr; } size_t get_copied() const { return copied; } size_t get_moved() const { return moved; } T * get() const noexcept { return hold_ptr; } private: T * hold_ptr; size_t copied; size_t moved; }; template bool operator==(const MockSharedPtr & lhs, std::nullptr_t) noexcept { if (lhs) { return true; } return false; } template void mustNotNull(const NotNull & nn) { RUNTIME_CHECK(nn != nullptr); } template void mustNotNullPtr(const NotNullShared & nn) { RUNTIME_CHECK(nn != nullptr); } template void mustNotNullUPtr(const NotNullUnique & nn) { RUNTIME_CHECK(nn != nullptr); } // Use volatile to prevent optimization. static volatile int MAYBE_NOT_ZERO = 0; template T * getNullPtr() { return reinterpret_cast(MAYBE_NOT_ZERO); } template void must_not_assign(const T &, const U &) { static_assert(!std::is_assignable_v); } template void must_not_construct(const U &) { static_assert(!std::is_constructible_v); } template void must_not_copy(const T &) { static_assert(!std::is_copy_constructible_v); } TEST(NotNullTest, Raw) { [[maybe_unused]] auto p1 = newNotNull(new int(1)); must_not_assign(p1, nullptr); EXPECT_ANY_THROW(p1 = newNotNull(getNullPtr())); } TEST(NotNullTest, Shared) { NotNull> p1 = makeNotNullShared(); auto p2 = std::move(p1); ASSERT_EQ(p2->moved, 0); ASSERT_EQ(p2->copied, 0); NotNull> p3(p2); ASSERT_EQ(p3->moved, 0); ASSERT_EQ(p3->copied, 0); p2->value = 1; ASSERT_EQ(p3->value, 1); must_not_assign(p1, nullptr); must_not_assign(p1, std::make_shared(nullptr)); } TEST(NotNullTest, MockShared) { Ball * ball = new Ball(); auto p1 = newNotNull>(MockSharedPtr(ball)); auto base_move = p1.as_nullable().get_moved(); auto p2 = std::move(p1); ASSERT_EQ(p2.as_nullable().get_moved(), base_move + 1); ASSERT_EQ(p2.as_nullable().get_copied(), 0); auto p3(p2); ASSERT_EQ(p3.as_nullable().get_moved(), base_move + 1); ASSERT_EQ(p3.as_nullable().get_copied(), 1); p2->value = 1; ASSERT_EQ(p3->value, 1); must_not_assign(p1, nullptr); } TEST(NotNullTest, Unique) { auto p1 = makeNotNullUnique(); auto p2 = std::move(p1); mustNotNullUPtr(p2); ASSERT_EQ(p2->moved, 0); ASSERT_EQ(p2->copied, 0); } namespace { void takesUnique(std::unique_ptr ptr) { // Either move of not_null or unqiue_ptr will not change `moved`. ASSERT_EQ(ptr->moved, 0); } void takesShared(std::shared_ptr ptr) { ASSERT_EQ(ptr->moved, 0); } void takesNotNullUnique(NotNullUnique ptr) { takesUnique(std::move(ptr).as_nullable()); } void takesNotNullShared(NotNullShared ptr) { takesShared(ptr.as_nullable()); } } // namespace TEST(NotNullTest, ToNullablePointer) { Ball::Asserts asserts; auto p1 = makeNotNullUnique(&asserts); takesNotNullUnique(std::move(p1)); ASSERT_EQ(asserts.destructed_count, 1); // Can't copy-constructs not_null> auto p3 = makeNotNullUnique(&asserts); must_not_copy(p3); auto p2 = makeNotNullShared(&asserts); takesNotNullShared(p2); ASSERT_EQ(asserts.destructed_count, 1); // destructs when return auto p2_1 = makeNotNullShared(&asserts); takesNotNullShared(std::move(p2_1)); ASSERT_EQ(asserts.destructed_count, 2); // destructs when return auto p2_2 = makeNotNullShared(&asserts); auto p2_2_1 = p2_2.as_nullable(); takesNotNullShared(std::move(p2_2)); ASSERT_EQ(asserts.destructed_count, 2); } namespace { void consNotNullPointer(std::unique_ptr ptr) { auto p = newNotNull(std::move(ptr)); ASSERT_EQ(p->copied, 0); ASSERT_EQ(p->moved, 0); } void consNotNullPointer(std::shared_ptr ptr) { auto p = newNotNull(std::move(ptr)); ASSERT_EQ(p->copied, 0); ASSERT_EQ(p->moved, 0); } } // namespace TEST(NotNullTest, ToNotNullPointer) { Ball::Asserts asserts; auto p1 = std::make_unique(&asserts); consNotNullPointer(std::move(p1)); ASSERT_EQ(asserts.destructed_count, 1); auto p2 = std::make_shared(&asserts); consNotNullPointer(std::move(p2)); ASSERT_EQ(asserts.destructed_count, 2); auto p2_1 = std::make_shared(&asserts); auto p2_1_1 = p2_1; consNotNullPointer(std::move(p2_1)); ASSERT_EQ(asserts.destructed_count, 2); } namespace { template void convertNotNullPointer(NotNull ptr) { NotNull p = cpp::bitwizeshift::not_null(std::move(ptr)); ASSERT_EQ(p->copied, 0); ASSERT_EQ(p->moved, 0); } } // namespace TEST(NotNullTest, Converts) { Ball::Asserts asserts; auto p1 = makeNotNullUnique(&asserts); convertNotNullPointer>(std::move(p1)); ASSERT_EQ(asserts.destructed_count, 1); // auto p2 = makeNotNullShared(&asserts); // convertNotNullPointer>(std::move(p2)); static_assert(NOT_NULL_NS_IMPL::detail:: not_null_is_implicit_convertible, std::unique_ptr &&>::value); static_assert(!NOT_NULL_NS_IMPL::detail:: not_null_is_implicit_convertible, std::shared_ptr &&>::value); static_assert(!NOT_NULL_NS_IMPL::detail:: not_null_is_explicit_convertible, std::unique_ptr &&>::value); static_assert(!NOT_NULL_NS_IMPL::detail:: not_null_is_explicit_convertible, std::shared_ptr &&>::value); } } // namespace DB::tests