blob: 700dc090dce93e72c057940d6097c6337f1aecfc [file]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Unittests for CheckedPtr and make_unique.
///
//===----------------------------------------------------------------------===//
#include "src/__support/CPP/type_traits.h"
#include "src/__support/CPP/unique_ptr.h"
#include "src/__support/CPP/utility.h"
#include "src/__support/alloc-checker.h"
#include "src/__support/checked_ptr.h"
#include "test/UnitTest/Test.h"
using LIBC_NAMESPACE::AllocChecker;
using LIBC_NAMESPACE::CheckedPtr;
using LIBC_NAMESPACE::make_unique;
using LIBC_NAMESPACE::cpp::is_constructible_v;
using LIBC_NAMESPACE::cpp::move;
using LIBC_NAMESPACE::cpp::unique_ptr;
// Verify compile-time properties:
// - Non-copyable and non-default-constructible.
// - Explicit conversion to bool is only allowed on non-const lvalues/rvalues.
static_assert(!is_constructible_v<CheckedPtr<int>>);
static_assert(!is_constructible_v<CheckedPtr<int>, const CheckedPtr<int> &>);
static_assert(is_constructible_v<bool, CheckedPtr<int> &>);
static_assert(!is_constructible_v<bool, const CheckedPtr<int> &>);
static_assert(is_constructible_v<bool, CheckedPtr<int[]> &>);
static_assert(!is_constructible_v<bool, const CheckedPtr<int[]> &>);
struct DestructTracker {
int *destruct_count = nullptr;
DestructTracker() = default;
DestructTracker(int *count) : destruct_count(count) {}
~DestructTracker() {
if (destruct_count)
(*destruct_count)++;
}
};
struct Foo {
int val;
Foo(int v) : val(v) {}
};
struct Base {
int val;
virtual ~Base() = default;
Base(int v) : val(v) {}
};
struct Derived : public Base {
Derived(int v) : Base(v) {}
};
struct CustomDeleter {
int *count;
void operator()(int *p) const {
(*count)++;
delete p;
}
};
struct CustomArrayDeleter {
int *count;
void operator()(int *p) const {
if (count)
(*count)++;
delete[] p;
}
};
TEST(LlvmLibcCheckedPtrTest, BasicMakeUniqueAndAccess) {
int destruct_count = 0;
{
auto ptr = make_unique<DestructTracker>(&destruct_count);
ASSERT_TRUE(static_cast<bool>(ptr));
// Once checked, access via const reference is valid and safe.
const auto &cref = ptr;
EXPECT_EQ(*cref->destruct_count, 0);
EXPECT_EQ(*(*cref).destruct_count, 0);
}
EXPECT_EQ(destruct_count, 1);
}
TEST(LlvmLibcCheckedPtrTest, NullptrCheck) {
CheckedPtr<int> ptr(nullptr);
EXPECT_FALSE(static_cast<bool>(ptr));
}
TEST(LlvmLibcCheckedPtrTest, MoveRequiresRecheck) {
int destruct_count = 0;
{
auto ptr1 = make_unique<DestructTracker>(&destruct_count);
EXPECT_TRUE(static_cast<bool>(ptr1));
// Even though ptr1 was checked, moving into ptr2 requires checking ptr2.
CheckedPtr<DestructTracker> ptr2(move(ptr1));
ASSERT_TRUE(static_cast<bool>(ptr2));
EXPECT_EQ(*ptr2->destruct_count, 0);
}
EXPECT_EQ(destruct_count, 1);
}
TEST(LlvmLibcCheckedPtrTest, MoveUncheckedThenCheck) {
int destruct_count = 0;
{
auto ptr1 = make_unique<DestructTracker>(&destruct_count);
// Move before checking: ptr1 is disarmed; ptr2 must be checked.
CheckedPtr<DestructTracker> ptr2(move(ptr1));
ASSERT_TRUE(static_cast<bool>(ptr2));
EXPECT_EQ(*ptr2->destruct_count, 0);
}
EXPECT_EQ(destruct_count, 1);
}
TEST(LlvmLibcCheckedPtrTest, MoveAssignment) {
int destruct_count1 = 0;
int destruct_count2 = 0;
{
auto ptr1 = make_unique<DestructTracker>(&destruct_count1);
EXPECT_TRUE(static_cast<bool>(ptr1));
auto ptr2 = make_unique<DestructTracker>(&destruct_count2);
EXPECT_TRUE(static_cast<bool>(ptr2));
ptr2 = move(ptr1);
EXPECT_EQ(destruct_count1, 0);
EXPECT_EQ(destruct_count2, 1);
EXPECT_TRUE(static_cast<bool>(ptr2));
}
EXPECT_EQ(destruct_count1, 1);
}
TEST(LlvmLibcCheckedPtrTest, Conversions) {
auto derived = make_unique<Derived>(100);
CheckedPtr<Base> base(move(derived));
ASSERT_TRUE(static_cast<bool>(base));
EXPECT_EQ(base->val, 100);
auto mut_int = make_unique<int>(42);
CheckedPtr<const int> const_int(move(mut_int));
ASSERT_TRUE(static_cast<bool>(const_int));
EXPECT_EQ(*const_int, 42);
}
TEST(LlvmLibcCheckedPtrTest, Array) {
int destruct_count = 0;
{
auto ptr = make_unique<DestructTracker[]>(3);
ASSERT_TRUE(static_cast<bool>(ptr));
ptr[0].destruct_count = &destruct_count;
ptr[1].destruct_count = &destruct_count;
ptr[2].destruct_count = &destruct_count;
EXPECT_EQ(destruct_count, 0);
}
EXPECT_EQ(destruct_count, 3);
}
TEST(LlvmLibcCheckedPtrTest, CustomDeleters) {
int obj_deletes = 0;
{
AllocChecker ac;
CheckedPtr<int, CustomDeleter> ptr(new (ac) int(7),
CustomDeleter{&obj_deletes});
ASSERT_TRUE(static_cast<bool>(ptr));
EXPECT_EQ(*ptr, 7);
EXPECT_EQ(obj_deletes, 0);
}
EXPECT_EQ(obj_deletes, 1);
int arr_deletes = 0;
{
AllocChecker ac;
CheckedPtr<int[], CustomArrayDeleter> arr(new (ac) int[2],
CustomArrayDeleter{&arr_deletes});
ASSERT_TRUE(static_cast<bool>(arr));
arr[0] = 10;
arr[1] = 20;
EXPECT_EQ(arr[0] + arr[1], 30);
EXPECT_EQ(arr_deletes, 0);
}
EXPECT_EQ(arr_deletes, 1);
}
TEST(LlvmLibcCheckedPtrTest, SelfSwap) {
auto ptr = make_unique<int>(1);
EXPECT_TRUE(static_cast<bool>(ptr));
// Self-move-assignment (which happens as a part of self-swap) must be a
// no-op.
LIBC_NAMESPACE::cpp::swap(ptr, ptr);
ASSERT_TRUE(static_cast<bool>(ptr));
EXPECT_EQ(*ptr, 1);
}
struct VoidDeleter {
void operator()(void *) const {}
};
TEST(LlvmLibcCheckedPtrTest, Void) {
CheckedPtr<void, VoidDeleter> ptr(nullptr);
EXPECT_FALSE(static_cast<bool>(ptr));
}
TEST(LlvmLibcCheckedPtrTest, TakeUnique) {
int destruct_count = 0;
unique_ptr<DestructTracker> owner;
{
auto ptr = make_unique<DestructTracker>(&destruct_count);
EXPECT_TRUE(static_cast<bool>(ptr));
owner = ptr.take_unique();
// `ptr` is now null and still checked, so destroying it must neither delete
// the object nor assert.
}
EXPECT_EQ(destruct_count, 0);
EXPECT_TRUE(static_cast<bool>(owner));
owner.reset();
EXPECT_EQ(destruct_count, 1);
}
#if !defined(NDEBUG) && LIBC_TEST_SUBPROCESS_TESTS
#if defined(LIBC_FULL_BUILD) && !defined(LIBC_COPT_USE_C_ASSERT)
#define EXPECT_ASSERT_FAILS(stmt) EXPECT_EXITS(stmt, 255)
#else
#define EXPECT_ASSERT_FAILS(stmt) EXPECT_DEATH(stmt, WITH_SIGNAL(-1))
#endif
TEST(LlvmLibcCheckedPtrDeathTest, UncheckedDestructionDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr = make_unique<int>(42);
(void)ptr;
});
}
TEST(LlvmLibcCheckedPtrDeathTest, MoveFromCheckedRequiresRecheckOrDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr1 = make_unique<int>(42);
(void)static_cast<bool>(ptr1);
CheckedPtr<int> ptr2(move(ptr1));
(void)*ptr2;
});
}
// In the tests below, the pointer is checked after the operation under test, so
// that its destruction cannot be the thing that triggers the assertion.
TEST(LlvmLibcCheckedPtrDeathTest, UncheckedDereferenceDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr = make_unique<int>(42);
(void)*ptr;
(void)static_cast<bool>(ptr);
});
}
TEST(LlvmLibcCheckedPtrDeathTest, UncheckedArrowDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr = make_unique<Foo>(42);
(void)ptr->val;
(void)static_cast<bool>(ptr);
});
}
TEST(LlvmLibcCheckedPtrDeathTest, UncheckedArraySubscriptDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr = make_unique<int[]>(3);
(void)ptr[0];
(void)static_cast<bool>(ptr);
});
}
TEST(LlvmLibcCheckedPtrDeathTest, OverwritingUncheckedViaMoveAssignmentDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr1 = make_unique<int>(1);
auto ptr2 = make_unique<int>(2);
(void)static_cast<bool>(ptr2);
ptr1 = move(ptr2);
(void)static_cast<bool>(ptr1);
});
}
TEST(LlvmLibcCheckedPtrDeathTest, UncheckedTakeUniqueDies) {
EXPECT_ASSERT_FAILS([] {
auto ptr = make_unique<int>(42);
(void)ptr.take_unique();
(void)static_cast<bool>(ptr);
});
}
#endif // !defined(NDEBUG) && LIBC_TEST_SUBPROCESS_TESTS