mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
d55c7573ed
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
607 lines
13 KiB
C++
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;
|
|
}
|