/* -*- 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 #include // For unique_ptr #include #include #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 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 UniqueA; typedef UniquePtr 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 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 u(new int, Deleter()); } static bool TestDefaultFreeGuts() { static_assert(std::is_same_v >, "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 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&> IntDeleterRef; typedef UniquePtr&> ADeleterRef; typedef UniquePtr&> 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 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 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(aPtr); } static void SetMallocedInt(UniquePtr& aPtr, int aI) { int* newPtr = static_cast(malloc(sizeof(int))); *newPtr = aI; aPtr.reset(newPtr); } static UniquePtr MallocedInt(int aI) { UniquePtr ptr(static_cast(malloc(sizeof(int))), free); *ptr = aI; return ptr; } static bool TestFunctionReferenceDeleter() { // Look for allocator mismatches and leaks to verify these bits UniquePtr i1(MallocedInt(17)); CHECK(*i1 == 17); SetMallocedInt(i1, 42); CHECK(*i1 == 42); // These bits use a custom deleter so we can instrument deletion. { UniquePtr i2 = UniquePtr(new int[42], DeleteIntFunction); CHECK(DeleteIntFunctionCallCount == 0); i2.reset(new int[76]); CHECK(DeleteIntFunctionCallCount == 1); } CHECK(DeleteIntFunctionCallCount == 2); return true; } template struct AppendNullptrTwice { AppendNullptrTwice() = default; bool operator()(Vector& vec) { CHECK(vec.append(nullptr)); CHECK(vec.append(nullptr)); return true; } }; static size_t AAfter; static size_t BAfter; static bool TestVectorGuts() { Vector vec; CHECK(vec.append(new B)); CHECK(vec.append(new A)); CHECK(AppendNullptrTwice()(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 IntArray; static_assert(sizeof(IntArray) == sizeof(int*), "stored most efficiently"); static bool TestArray() { static_assert(std::is_same_v >, "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 a1(new A[17]); static_assert(sizeof(a1) == sizeof(A*), "stored most efficiently"); UniquePtr a2(new A[5], DefaultDelete()); a2.reset(nullptr); a2.reset(new A[17]); a2 = nullptr; UniquePtr a3(nullptr); a3.reset(new A[7]); return true; } struct Q { Q() = default; Q(const Q&) = default; Q(Q&, char) {} template Q(Q, T&&, int) {} Q(int, long, double, void*) {} }; static int randomInt() { return 4; } static bool TestMakeUnique() { UniquePtr a1(MakeUnique()); UniquePtr a2(MakeUnique(4)); // no args, easy UniquePtr q0(MakeUnique()); // temporary bound to const lval ref UniquePtr q1(MakeUnique(Q())); // passing through a non-const lval ref UniquePtr q2(MakeUnique(*q1, 'c')); // pass by copying, forward a temporary, pass by value UniquePtr q3(MakeUnique(Q(), UniquePtr(), randomInt())); // various type mismatching to test "fuzzy" forwarding UniquePtr q4(MakeUnique('s', 66LL, 3.141592654, &q3)); UniquePtr c1(MakeUnique(5)); return true; } static bool TestVoid() { // UniquePtr supports all operations except operator*() and // operator->(). UniqueFreePtr p1(malloc(1)); UniqueFreePtr p2; auto x = p1.get(); CHECK(x != nullptr); CHECK((std::is_same_v)); 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 f; (void)AllocByOutvar(mozilla::TempPtrToSetter(&f)); CHECK(sFooRefcount == 1); } CHECK(sFooRefcount == 0); { std::unique_ptr 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; }