/* -*- 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;
}