mfbt/tests/TestUniquePtr.cpp
author Brian Hackett <bhackett1024@gmail.com>
Wed, 14 Nov 2018 16:09:58 -1000
changeset 446931 1c7fc8389e012c987347efefca6b35f3948b742a
parent 421895 17b140524c6ee9e1263175212dec5231bfe3059f
child 450554 09c71a7cf75aeaf2963050e315276fb9a866fd62
permissions -rw-r--r--
Bug 1507359 Part 2 - Bindings and internal changes to allow ReplayDebugger to control child pausing/resuming, r=mccr8.

/* -*- 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 "mozilla/Assertions.h"
#include "mozilla/Compiler.h"
#include "mozilla/Move.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Vector.h"

#include <stddef.h>

using mozilla::DefaultDelete;
using mozilla::IsSame;
using mozilla::MakeUnique;
using mozilla::Swap;
using mozilla::UniquePtr;
using mozilla::UniqueFreePtr;
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() {}
    void operator()(int*p) {
      delete p;
    }
  };
  UniquePtr<Ptr, Deleter> u(new int, Deleter());
}

static bool
TestDefaultFreeGuts()
{
  static_assert(IsSame<NewInt::DeleterType, DefaultDelete<int> >::value,
                "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);

  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() {}

  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() {}

  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(IsSame<IntArray::DeleterType, DefaultDelete<int[]> >::value,
                "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);

  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() {}
  Q(const Q&) {}

  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((IsSame<decltype(x), void*>::value));

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