diff options
Diffstat (limited to '')
-rw-r--r-- | mfbt/tests/TestUniquePtr.cpp | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/mfbt/tests/TestUniquePtr.cpp b/mfbt/tests/TestUniquePtr.cpp new file mode 100644 index 0000000000..3a7b3e3771 --- /dev/null +++ b/mfbt/tests/TestUniquePtr.cpp @@ -0,0 +1,575 @@ +/* -*- 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 <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_ASSERT(cond, "Failed assertion: " #c); \ + if (!cond) { \ + return false; \ + } \ + } 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; +} + +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; + } + return 0; +} |