gecko-dev/mfbt/tests/TestUniquePtr.cpp
Chris Peterson d55c7573ed Bug 1891332 - Replace debug MOZ_ASSERT with MOZ_RELEASE_ASSERT in MFBT tests. r=xpcom-reviewers,emilio
MOZ_ASSERT is only checked in debug builds, so release builds' tests are not checking these assertions.

Depends on D207373

Differential Revision: https://phabricator.services.mozilla.com/D207374
2024-04-17 03:26:29 +00:00

607 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stddef.h>
#include <memory> // For unique_ptr
#include <type_traits>
#include <utility>
#include "mozilla/Assertions.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Vector.h"
using mozilla::DefaultDelete;
using mozilla::MakeUnique;
using mozilla::UniqueFreePtr;
using mozilla::UniquePtr;
using mozilla::Vector;
#define CHECK(c) \
do { \
bool cond = !!(c); \
MOZ_RELEASE_ASSERT(cond, "Test failed: " #c); \
} while (false)
typedef UniquePtr<int> NewInt;
static_assert(sizeof(NewInt) == sizeof(int*), "stored most efficiently");
static size_t gADestructorCalls = 0;
struct A {
public:
A() : mX(0) {}
virtual ~A() { gADestructorCalls++; }
int mX;
};
static size_t gBDestructorCalls = 0;
struct B : public A {
public:
B() : mY(1) {}
~B() { gBDestructorCalls++; }
int mY;
};
typedef UniquePtr<A> UniqueA;
typedef UniquePtr<B, UniqueA::DeleterType> UniqueB; // permit interconversion
static_assert(sizeof(UniqueA) == sizeof(A*), "stored most efficiently");
static_assert(sizeof(UniqueB) == sizeof(B*), "stored most efficiently");
struct DeleterSubclass : UniqueA::DeleterType {};
typedef UniquePtr<B, DeleterSubclass> UniqueC;
static_assert(sizeof(UniqueC) == sizeof(B*), "stored most efficiently");
static UniqueA ReturnUniqueA() { return UniqueA(new B); }
static UniqueA ReturnLocalA() {
UniqueA a(new A);
return a;
}
static void TestDeleterType() {
// Make sure UniquePtr will use its deleter's pointer type if it defines one.
typedef int* Ptr;
struct Deleter {
typedef Ptr pointer;
Deleter() = default;
void operator()(int* p) { delete p; }
};
UniquePtr<Ptr, Deleter> u(new int, Deleter());
}
static bool TestDefaultFreeGuts() {
static_assert(std::is_same_v<NewInt::DeleterType, DefaultDelete<int> >,
"weird deleter?");
NewInt n1(new int);
CHECK(n1);
CHECK(n1.get() != nullptr);
n1 = nullptr;
CHECK(!n1);
CHECK(n1.get() == nullptr);
int* p1 = new int;
n1.reset(p1);
CHECK(n1);
NewInt n2(std::move(n1));
CHECK(!n1);
CHECK(n1.get() == nullptr);
CHECK(n2.get() == p1);
std::swap(n1, n2);
CHECK(n1.get() == p1);
CHECK(n2.get() == nullptr);
n1.swap(n2);
CHECK(n1.get() == nullptr);
CHECK(n2.get() == p1);
delete n2.release();
CHECK(n1.get() == nullptr);
CHECK(n2 == nullptr);
CHECK(nullptr == n2);
int* p2 = new int;
int* p3 = new int;
n1.reset(p2);
n2.reset(p3);
CHECK(n1.get() == p2);
CHECK(n2.get() == p3);
n1.swap(n2);
CHECK(n2 != nullptr);
CHECK(nullptr != n2);
CHECK(n2.get() == p2);
CHECK(n1.get() == p3);
UniqueA a1;
CHECK(a1 == nullptr);
a1.reset(new A);
CHECK(gADestructorCalls == 0);
CHECK(a1->mX == 0);
B* bp1 = new B;
bp1->mX = 5;
CHECK(gBDestructorCalls == 0);
a1.reset(bp1);
CHECK(gADestructorCalls == 1);
CHECK(a1->mX == 5);
a1.reset(nullptr);
CHECK(gADestructorCalls == 2);
CHECK(gBDestructorCalls == 1);
B* bp2 = new B;
UniqueB b1(bp2);
UniqueA a2(nullptr);
a2 = std::move(b1);
CHECK(gADestructorCalls == 2);
CHECK(gBDestructorCalls == 1);
UniqueA a3(std::move(a2));
a3 = nullptr;
CHECK(gADestructorCalls == 3);
CHECK(gBDestructorCalls == 2);
B* bp3 = new B;
bp3->mX = 42;
UniqueB b2(bp3);
UniqueA a4(std::move(b2));
CHECK(b2.get() == nullptr);
CHECK((*a4).mX == 42);
CHECK(gADestructorCalls == 3);
CHECK(gBDestructorCalls == 2);
UniqueA a5(new A);
UniqueB b3(new B);
a5 = std::move(b3);
CHECK(gADestructorCalls == 4);
CHECK(gBDestructorCalls == 2);
ReturnUniqueA();
CHECK(gADestructorCalls == 5);
CHECK(gBDestructorCalls == 3);
ReturnLocalA();
CHECK(gADestructorCalls == 6);
CHECK(gBDestructorCalls == 3);
UniqueA a6(ReturnLocalA());
a6 = nullptr;
CHECK(gADestructorCalls == 7);
CHECK(gBDestructorCalls == 3);
UniqueC c1(new B);
UniqueA a7(new B);
a7 = std::move(c1);
CHECK(gADestructorCalls == 8);
CHECK(gBDestructorCalls == 4);
c1.reset(new B);
UniqueA a8(std::move(c1));
CHECK(gADestructorCalls == 8);
CHECK(gBDestructorCalls == 4);
// These smart pointers still own B resources.
CHECK(a4);
CHECK(a5);
CHECK(a7);
CHECK(a8);
return true;
}
static bool TestDefaultFree() {
CHECK(TestDefaultFreeGuts());
CHECK(gADestructorCalls == 12);
CHECK(gBDestructorCalls == 8);
return true;
}
static size_t FreeClassCounter = 0;
struct FreeClass {
public:
FreeClass() = default;
void operator()(int* aPtr) {
FreeClassCounter++;
delete aPtr;
}
};
typedef UniquePtr<int, FreeClass> NewIntCustom;
static_assert(sizeof(NewIntCustom) == sizeof(int*), "stored most efficiently");
static bool TestFreeClass() {
CHECK(FreeClassCounter == 0);
{
NewIntCustom n1(new int);
CHECK(FreeClassCounter == 0);
}
CHECK(FreeClassCounter == 1);
NewIntCustom n2;
{
NewIntCustom n3(new int);
CHECK(FreeClassCounter == 1);
n2 = std::move(n3);
}
CHECK(FreeClassCounter == 1);
n2 = nullptr;
CHECK(FreeClassCounter == 2);
n2.reset(nullptr);
CHECK(FreeClassCounter == 2);
n2.reset(new int);
n2.reset();
CHECK(FreeClassCounter == 3);
NewIntCustom n4(new int, FreeClass());
CHECK(FreeClassCounter == 3);
n4.reset(new int);
CHECK(FreeClassCounter == 4);
n4.reset();
CHECK(FreeClassCounter == 5);
FreeClass f;
NewIntCustom n5(new int, f);
CHECK(FreeClassCounter == 5);
int* p = n5.release();
CHECK(FreeClassCounter == 5);
delete p;
return true;
}
typedef UniquePtr<int, DefaultDelete<int>&> IntDeleterRef;
typedef UniquePtr<A, DefaultDelete<A>&> ADeleterRef;
typedef UniquePtr<B, DefaultDelete<A>&> BDeleterRef;
static_assert(sizeof(IntDeleterRef) > sizeof(int*),
"has to be heavier than an int* to store the reference");
static_assert(sizeof(ADeleterRef) > sizeof(A*),
"has to be heavier than an A* to store the reference");
static_assert(sizeof(BDeleterRef) > sizeof(int*),
"has to be heavier than a B* to store the reference");
static bool TestReferenceDeleterGuts() {
DefaultDelete<int> delInt;
IntDeleterRef id1(new int, delInt);
IntDeleterRef id2(std::move(id1));
CHECK(id1 == nullptr);
CHECK(nullptr != id2);
CHECK(&id1.get_deleter() == &id2.get_deleter());
IntDeleterRef id3(std::move(id2));
DefaultDelete<A> delA;
ADeleterRef a1(new A, delA);
a1.reset(nullptr);
a1.reset(new B);
a1 = nullptr;
BDeleterRef b1(new B, delA);
a1 = std::move(b1);
BDeleterRef b2(new B, delA);
ADeleterRef a2(std::move(b2));
return true;
}
static bool TestReferenceDeleter() {
gADestructorCalls = 0;
gBDestructorCalls = 0;
CHECK(TestReferenceDeleterGuts());
CHECK(gADestructorCalls == 4);
CHECK(gBDestructorCalls == 3);
gADestructorCalls = 0;
gBDestructorCalls = 0;
return true;
}
typedef void (&FreeSignature)(void*);
static size_t DeleteIntFunctionCallCount = 0;
static void DeleteIntFunction(void* aPtr) {
DeleteIntFunctionCallCount++;
delete static_cast<int*>(aPtr);
}
static void SetMallocedInt(UniquePtr<int, FreeSignature>& aPtr, int aI) {
int* newPtr = static_cast<int*>(malloc(sizeof(int)));
*newPtr = aI;
aPtr.reset(newPtr);
}
static UniquePtr<int, FreeSignature> MallocedInt(int aI) {
UniquePtr<int, FreeSignature> ptr(static_cast<int*>(malloc(sizeof(int))),
free);
*ptr = aI;
return ptr;
}
static bool TestFunctionReferenceDeleter() {
// Look for allocator mismatches and leaks to verify these bits
UniquePtr<int, FreeSignature> i1(MallocedInt(17));
CHECK(*i1 == 17);
SetMallocedInt(i1, 42);
CHECK(*i1 == 42);
// These bits use a custom deleter so we can instrument deletion.
{
UniquePtr<int, FreeSignature> i2 =
UniquePtr<int, FreeSignature>(new int[42], DeleteIntFunction);
CHECK(DeleteIntFunctionCallCount == 0);
i2.reset(new int[76]);
CHECK(DeleteIntFunctionCallCount == 1);
}
CHECK(DeleteIntFunctionCallCount == 2);
return true;
}
template <typename T>
struct AppendNullptrTwice {
AppendNullptrTwice() = default;
bool operator()(Vector<T>& vec) {
CHECK(vec.append(nullptr));
CHECK(vec.append(nullptr));
return true;
}
};
static size_t AAfter;
static size_t BAfter;
static bool TestVectorGuts() {
Vector<UniqueA> vec;
CHECK(vec.append(new B));
CHECK(vec.append(new A));
CHECK(AppendNullptrTwice<UniqueA>()(vec));
CHECK(vec.append(new B));
size_t initialLength = vec.length();
UniqueA* begin = vec.begin();
bool appendA = true;
do {
CHECK(appendA ? vec.append(new A) : vec.append(new B));
appendA = !appendA;
} while (begin == vec.begin());
size_t numAppended = vec.length() - initialLength;
BAfter = numAppended / 2;
AAfter = numAppended - numAppended / 2;
CHECK(gADestructorCalls == 0);
CHECK(gBDestructorCalls == 0);
return true;
}
static bool TestVector() {
gADestructorCalls = 0;
gBDestructorCalls = 0;
CHECK(TestVectorGuts());
CHECK(gADestructorCalls == 3 + AAfter + BAfter);
CHECK(gBDestructorCalls == 2 + BAfter);
return true;
}
typedef UniquePtr<int[]> IntArray;
static_assert(sizeof(IntArray) == sizeof(int*), "stored most efficiently");
static bool TestArray() {
static_assert(std::is_same_v<IntArray::DeleterType, DefaultDelete<int[]> >,
"weird deleter?");
IntArray n1(new int[5]);
CHECK(n1);
CHECK(n1.get() != nullptr);
n1 = nullptr;
CHECK(!n1);
CHECK(n1.get() == nullptr);
int* p1 = new int[42];
n1.reset(p1);
CHECK(n1);
IntArray n2(std::move(n1));
CHECK(!n1);
CHECK(n1.get() == nullptr);
CHECK(n2.get() == p1);
std::swap(n1, n2);
CHECK(n1.get() == p1);
CHECK(n2.get() == nullptr);
n1.swap(n2);
CHECK(n1.get() == nullptr);
CHECK(n2.get() == p1);
delete[] n2.release();
CHECK(n1.get() == nullptr);
CHECK(n2.get() == nullptr);
int* p2 = new int[7];
int* p3 = new int[42];
n1.reset(p2);
n2.reset(p3);
CHECK(n1.get() == p2);
CHECK(n2.get() == p3);
n1.swap(n2);
CHECK(n2.get() == p2);
CHECK(n1.get() == p3);
n1 = std::move(n2);
CHECK(n1.get() == p2);
n1 = std::move(n2);
CHECK(n1.get() == nullptr);
UniquePtr<A[]> a1(new A[17]);
static_assert(sizeof(a1) == sizeof(A*), "stored most efficiently");
UniquePtr<A[]> a2(new A[5], DefaultDelete<A[]>());
a2.reset(nullptr);
a2.reset(new A[17]);
a2 = nullptr;
UniquePtr<A[]> a3(nullptr);
a3.reset(new A[7]);
return true;
}
struct Q {
Q() = default;
Q(const Q&) = default;
Q(Q&, char) {}
template <typename T>
Q(Q, T&&, int) {}
Q(int, long, double, void*) {}
};
static int randomInt() { return 4; }
static bool TestMakeUnique() {
UniquePtr<int> a1(MakeUnique<int>());
UniquePtr<long> a2(MakeUnique<long>(4));
// no args, easy
UniquePtr<Q> q0(MakeUnique<Q>());
// temporary bound to const lval ref
UniquePtr<Q> q1(MakeUnique<Q>(Q()));
// passing through a non-const lval ref
UniquePtr<Q> q2(MakeUnique<Q>(*q1, 'c'));
// pass by copying, forward a temporary, pass by value
UniquePtr<Q> q3(MakeUnique<Q>(Q(), UniquePtr<int>(), randomInt()));
// various type mismatching to test "fuzzy" forwarding
UniquePtr<Q> q4(MakeUnique<Q>('s', 66LL, 3.141592654, &q3));
UniquePtr<char[]> c1(MakeUnique<char[]>(5));
return true;
}
static bool TestVoid() {
// UniquePtr<void> supports all operations except operator*() and
// operator->().
UniqueFreePtr<void> p1(malloc(1));
UniqueFreePtr<void> p2;
auto x = p1.get();
CHECK(x != nullptr);
CHECK((std::is_same_v<decltype(x), void*>));
p2.reset(p1.release());
CHECK(p1.get() == nullptr);
CHECK(p2.get() != nullptr);
p1 = std::move(p2);
CHECK(p1);
CHECK(!p2);
p1.swap(p2);
CHECK(!p1);
CHECK(p2);
p2 = nullptr;
CHECK(!p2);
return true;
}
static bool TestTempPtrToSetter() {
static int sFooRefcount = 0;
struct Foo {
Foo() { sFooRefcount += 1; }
~Foo() { sFooRefcount -= 1; }
};
const auto AllocByOutvar = [](Foo** out) -> bool {
*out = new Foo;
return true;
};
{
UniquePtr<Foo> f;
(void)AllocByOutvar(mozilla::TempPtrToSetter(&f));
CHECK(sFooRefcount == 1);
}
CHECK(sFooRefcount == 0);
{
std::unique_ptr<Foo> f;
(void)AllocByOutvar(mozilla::TempPtrToSetter(&f));
CHECK(sFooRefcount == 1);
}
CHECK(sFooRefcount == 0);
return true;
}
int main() {
TestDeleterType();
if (!TestDefaultFree()) {
return 1;
}
if (!TestFreeClass()) {
return 1;
}
if (!TestReferenceDeleter()) {
return 1;
}
if (!TestFunctionReferenceDeleter()) {
return 1;
}
if (!TestVector()) {
return 1;
}
if (!TestArray()) {
return 1;
}
if (!TestMakeUnique()) {
return 1;
}
if (!TestVoid()) {
return 1;
}
if (!TestTempPtrToSetter()) {
return 1;
}
return 0;
}