summaryrefslogtreecommitdiffstats
path: root/mfbt/tests/TestUniquePtr.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /mfbt/tests/TestUniquePtr.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mfbt/tests/TestUniquePtr.cpp')
-rw-r--r--mfbt/tests/TestUniquePtr.cpp575
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..1094f1410d
--- /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;
+}