osptr.cpp   [plain text]


#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++11-extensions"

#include <darwintest.h>
#include <darwintest_utils.h>
#include <stdio.h>
#include <assert.h>
#include <typeinfo>

#if 0
# define OSPTR_LOG T_LOG
#elif 0
# define OSPTR_LOG printf
#else
# define OSPTR_LOG(x...)  do { } while(0)
#endif

T_GLOBAL_META(
	T_META_NAMESPACE("osptr"),
	T_META_CHECK_LEAKS(false),
	T_META_RUN_CONCURRENTLY(true)
	);

static int num_instances = 0;
static int num_retains = 0;
static int num_releases = 0;

class OSMetaClassBase
{
	static int id_counter;
	static OSMetaClassBase *freelist;

public:
	int inst_id;
	mutable int refcount;
	mutable OSMetaClassBase *next;
	static void *type_id;

	OSMetaClassBase() : refcount(1), next(nullptr)
	{
		inst_id = id_counter++;
		num_instances++;
		OSPTR_LOG("[%p, %d] constructed\n", this, inst_id);
	}

	virtual ~OSMetaClassBase()
	{
		OSPTR_LOG("[%p, %d] destroyed\n", this, inst_id);
	}

	virtual void
	retain() const
	{
		T_QUIET; T_EXPECT_GT_INT(refcount, 0, "Instance resurrected");
		refcount++;
		num_retains++;
		OSPTR_LOG("[%p, %d] retain, refcount=%d\n", this, inst_id, refcount);
	}

	virtual void
	release() const
	{
		T_QUIET; T_EXPECT_GT_INT(refcount, 0, "Double free");
		refcount--;
		num_releases++;
		OSPTR_LOG("[%p, %d] release, refcount=%d\n", this, inst_id, refcount);

		/*
		 * Don't delete the object, but keep it around so that we
		 * can detect double frees
		 */
		if (refcount == 0) {
			num_instances--;
			this->next = freelist;
			freelist = const_cast<OSMetaClassBase *>(this);
		}
	}

	virtual void
	taggedRetain(void *tag) const
	{
		OSPTR_LOG("tag[%p] ", tag);
		retain();
	}

	virtual void
	taggedRelease(void *tag) const
	{
		OSPTR_LOG("tag[%p] ", tag);
		release();
	}
};

int OSMetaClassBase::id_counter;
OSMetaClassBase *OSMetaClassBase::freelist;

void *OSMetaClassBase::type_id;

#define OSTypeID(T) T::type_id
#define OSTypeAlloc(T) new T
#define OSDynamicCast(T, p) dynamic_cast<T *>(p)

#define LIBKERN_SMART_POINTERS
#include <libkern/c++/OSPtr.h>

class Base : public OSMetaClassBase {
public:
	Base() : OSMetaClassBase()
	{
	}
};

class Derived : public Base {
public:
	Derived() : Base()
	{
	}
};

class Other : public OSMetaClassBase {
public:
	Other() : OSMetaClassBase()
	{
	}
};

typedef OSPtr<Base> BasePtr;
typedef OSPtr<Derived> DerivedPtr;
typedef OSPtr<Other> OtherPtr;

static void
default_constructor()
{
	BasePtr a;
	T_ASSERT_NULL(a.get(), "Default NULL construction");
	T_ASSERT_EQ_INT(num_instances, 0, "No instances created");
}

static void
null_constructor()
{
	BasePtr a(nullptr);
	T_ASSERT_NULL(a.get(), "Default NULL construction");
	T_ASSERT_EQ_INT(num_instances, 0, "No instances created");
}

static void
raw_constructor()
{
	Base *a = new Base();
	T_ASSERT_EQ_INT(num_instances, 1, "Created instance");

	{
		BasePtr p(a);

		T_ASSERT_EQ_INT(num_instances, 1, "No new instance");
		T_ASSERT_EQ_PTR(p.get(), a, "osptr bound to correct object");
		T_ASSERT_EQ_INT(a->refcount, 2, "Object refcount incremented");
	}

	T_ASSERT_EQ_INT(a->refcount, 1, "Object refcount decremented");
	a->release();
	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
}

static void
alloc()
{
	BasePtr a = BasePtr::alloc();

	T_ASSERT_NOTNULL(a.get(), "osptr seated");
	T_ASSERT_EQ_INT(num_instances, 1, "Instance created");
	T_ASSERT_EQ_INT(a->refcount, 1, "Reference created");
}

static void
destroy()
{
	{
		BasePtr a = BasePtr::alloc();
		T_ASSERT_EQ_INT(num_instances, 1, "Instance created");
	}

	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
}

static void
copy()
{
	BasePtr a = BasePtr::alloc();
	BasePtr b;
	int a_id = a->inst_id;

	BasePtr a_copy(a);

	T_ASSERT_EQ_INT(a_copy->inst_id, a_id, NULL);
	T_ASSERT_EQ_INT(a->refcount, 2, NULL);
	T_ASSERT_EQ_INT(a_copy->refcount, 2, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 1, NULL);

	BasePtr b_copy(b);
	T_ASSERT_NULL(b_copy.get(), "Copy null osptr");

	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 1, NULL);

	BasePtr a_copy2 = a;
	T_ASSERT_EQ_PTR(a_copy2.get(), a.get(), NULL);

	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 2, NULL);
	T_EXPECT_EQ_INT(num_releases, 0, NULL);
}

static void
copy_subclass()
{
	auto a = DerivedPtr::alloc();
	BasePtr b(a);

	T_ASSERT_EQ_PTR(a.get(), b.get(), NULL);
	T_ASSERT_EQ_INT(b->refcount, 2, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);

	a = nullptr;
	T_ASSERT_NOTNULL(b.get(), NULL);
	T_ASSERT_EQ_INT(b->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);
}

static void
assign()
{
	int a_id, b_id;

	BasePtr p;
	BasePtr a = BasePtr::alloc();
	BasePtr b = BasePtr::alloc();

	a_id = a->inst_id;
	b_id = b->inst_id;

	p = a;

	T_ASSERT_EQ_PTR(p.get(), a.get(), "Assigned osptr references same object");
	T_ASSERT_EQ_INT(p->inst_id, a_id, NULL);
	T_ASSERT_EQ_INT(a->refcount, 2, "Assigned osptr bumps refcount");
	T_QUIET; T_ASSERT_TRUE(b->refcount == 1, NULL);

	p = b;

	T_ASSERT_EQ_PTR(p.get(), b.get(), "Assigned osptr references same object");
	T_ASSERT_EQ_INT(p->inst_id, b_id, NULL);
	T_ASSERT_EQ_INT(a->refcount, 1, "Previous assignee drops reference");
	T_ASSERT_EQ_INT(b->refcount, 2, "New assignee bumps reference");

	T_ASSERT_EQ_INT(a->inst_id, a_id, NULL);
	T_ASSERT_EQ_INT(b->inst_id, b_id, NULL);

	a = nullptr;

	T_ASSERT_EQ_INT(num_instances, 1, "Assignment to null releases object");

	b = nullptr;
	p = nullptr;

	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
}

static void
assign_raw()
{
	Base *a1 = new Base();
	Base *a2 = new Base();

	{
		BasePtr p;

		p = a1;
		T_ASSERT_EQ_PTR(p.get(), a1, NULL);
		T_ASSERT_EQ_INT(a1->refcount, 2, NULL);
		T_ASSERT_EQ_INT(a2->refcount, 1, NULL);

		p = a2;
		T_ASSERT_EQ_PTR(p.get(), a2, NULL);
		T_ASSERT_EQ_INT(a1->refcount, 1, NULL);
		T_ASSERT_EQ_INT(a2->refcount, 2, NULL);
	}

	T_ASSERT_EQ_INT(a1->refcount, 1, NULL);
	T_ASSERT_EQ_INT(a2->refcount, 1, NULL);

	a1->release();
	a2->release();

	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
}

static void
assign_null()
{
	BasePtr a = BasePtr::alloc();
	T_ASSERT_EQ_INT(num_instances, 1, NULL);

	a = nullptr;

	T_ASSERT_NULL(a.get(), NULL);
	T_ASSERT_EQ_INT(num_instances, 0, "No instances created");

	a = BasePtr::alloc();
	BasePtr b(a.get());

	T_ASSERT_EQ_INT(a->refcount, 2, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);

	b = nullptr;

	T_ASSERT_EQ_INT(a->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);

	a = nullptr;

	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
}

static void
assign_subclass()
{
	int a_id, b_id;

	OSPtr<OSMetaClassBase> base;
	BasePtr a = BasePtr::alloc();
	BasePtr b = BasePtr::alloc();

	a_id = a->inst_id;
	b_id = b->inst_id;

	base = a;

	T_ASSERT_TRUE(base.get() == static_cast<OSMetaClassBase *>(a.get()), NULL);
	T_ASSERT_TRUE(base->inst_id == a_id, NULL);
	T_ASSERT_TRUE(a->refcount == 2, NULL);
	T_ASSERT_TRUE(b->refcount == 1, NULL);

	base = b;

	T_ASSERT_TRUE(base.get() == static_cast<OSMetaClassBase *>(b.get()), NULL);
	T_ASSERT_TRUE(base->inst_id == b_id, NULL);
	T_ASSERT_TRUE(a->refcount == 1, NULL);
	T_ASSERT_TRUE(b->refcount == 2, NULL);

	T_ASSERT_TRUE(a->inst_id == a_id, NULL);
	T_ASSERT_TRUE(b->inst_id == b_id, NULL);

	a = nullptr;

	T_ASSERT_TRUE(num_instances == 1, NULL);

	b = nullptr;
	base = nullptr;

	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
}

static void
assign_compatible()
{
	OSPtr<Base> a = OSPtr<Base>::alloc();
	OSPtr<const Base> b = a;
	T_ASSERT_EQ_PTR(a.get(), b.get(), NULL);

	OSPtr<Derived> c = OSPtr<Derived>::alloc();
	OSPtr<Base> d = c;
	T_ASSERT_EQ_PTR(c.get(), d.get(), NULL);
}

static void
move()
{
	OSPtr<const Base> a = OSPtr<const Base>::alloc();
	int a_id = a->inst_id;

	OSPtr<const Base> b(os::move(a));

	T_ASSERT_TRUE(a.get() == NULL, NULL);
	T_ASSERT_TRUE(b->inst_id == a_id, NULL);
	T_ASSERT_TRUE(b->refcount == 1, NULL);
	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 0, NULL);
}

static void
move_assign()
{
	OSPtr<const Base> a = OSPtr<const Base>::alloc();
	OSPtr<const Base> b = OSPtr<const Base>::alloc();
	int a_id = a->inst_id;
	int b_id = b->inst_id;

	OSPtr<const Base> d;

	d = os::move(a);

	T_ASSERT_TRUE(a.get() == NULL, NULL);
	T_ASSERT_TRUE(d->inst_id == a_id, NULL);
	T_ASSERT_TRUE(d->refcount == 1, NULL);
	T_ASSERT_TRUE(num_instances == 2, NULL);

	d = os::move(b);
	T_ASSERT_TRUE(a.get() == NULL, NULL);
	T_ASSERT_TRUE(b.get() == NULL, NULL);
	T_ASSERT_TRUE(d->inst_id == b_id, NULL);
	T_ASSERT_TRUE(d->refcount == 1, NULL);
	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 0, NULL);
}

static void
move_assign_null()
{
	BasePtr a = BasePtr::alloc();
	BasePtr b = a;

	T_EXPECT_EQ_INT(num_retains, 1, NULL);

	a = os::move(nullptr);

	T_ASSERT_TRUE(a.get() == NULL, NULL);
	T_ASSERT_TRUE(b->refcount == 1, NULL);

	b = os::move(nullptr);

	T_ASSERT_EQ_INT(num_instances, 0, "All instances released");
	T_EXPECT_EQ_INT(num_retains, 1, NULL);
}

static void
move_assign_raw()
{
	BasePtr a = BasePtr::alloc();
	Base *b = new Base;
	Base *tmp = b;

	T_ASSERT_EQ_INT(num_instances, 2, NULL);

	a = os::move(tmp);

	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_ASSERT_NULL(tmp, NULL);
	T_ASSERT_EQ_PTR(a.get(), b, NULL);
	T_ASSERT_EQ_INT(a->refcount, 2, NULL);
	b->release();
	T_ASSERT_EQ_INT(a->refcount, 1, NULL);
}

static void
move_assign_subclass()
{
	auto a = DerivedPtr::alloc();
	BasePtr b;

	b = os::move(a);

	T_ASSERT_NULL(a.get(), NULL);
	T_ASSERT_NOTNULL(b.get(), NULL);
	T_ASSERT_EQ_INT(b->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);
}

static void
move_assign_self()
{
	OSPtr<const Base> a = OSPtr<const Base>::alloc();
	int a_id = a->inst_id;

	a = os::move(a);

	T_ASSERT_NOTNULL(a.get(), "osptr seated");
	T_ASSERT_TRUE(a->inst_id == a_id, NULL);
	T_ASSERT_TRUE(a->refcount == 1, NULL);
	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 0, NULL);
}

static void
test_const_cast()
{
	OSPtr<const Base> a = OSPtr<const Base>::alloc();

	OSPtr<Base> b;

	b = a.const_pointer_cast<Base>();

	T_ASSERT_TRUE(a.get() == b.get(), NULL);
	T_ASSERT_TRUE(a->refcount == 2, NULL);
	T_ASSERT_TRUE(b->refcount == 2, NULL);

	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 1, NULL);
}

static void
const_cast_move()
{
	OSPtr<const Base> a = OSPtr<const Base>::alloc();
	int a_id = a->inst_id;

	OSPtr<Base> b;

	b = os::move(a).const_pointer_cast<Base>();

	T_ASSERT_TRUE(a.get() == NULL, NULL);
	T_ASSERT_TRUE(b->inst_id == a_id, NULL);
	T_ASSERT_TRUE(b->refcount == 1, NULL);

	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 0, NULL);
}

static void
const_cast_move_self()
{
	BasePtr a = BasePtr::alloc();
	int a_id = a->inst_id;

	a = os::move(a).const_pointer_cast<Base>();

	T_ASSERT_NOTNULL(a.get(), "osptr seated");
	T_ASSERT_TRUE(a->inst_id == a_id, NULL);
	T_ASSERT_TRUE(a->refcount == 1, NULL);
	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_ASSERT_TRUE(num_retains == 0, NULL);
}

static void
test_static_cast()
{
	DerivedPtr a = DerivedPtr::alloc();

	BasePtr b;

	b = a.static_pointer_cast<Base>();

	T_ASSERT_TRUE(a.get() == b.get(), NULL);
	T_ASSERT_TRUE(a->refcount == 2, NULL);
	T_ASSERT_TRUE(b->refcount == 2, NULL);

	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_EXPECT_TRUE(num_retains == 1, NULL);
}

static void
static_cast_move()
{
	DerivedPtr a = DerivedPtr::alloc();
	int a_id = a->inst_id;

	BasePtr b;

	b = os::move(a).static_pointer_cast<Base>();

	T_ASSERT_NULL(a.get(), NULL);
	T_ASSERT_EQ_INT(b->inst_id, a_id, NULL);
	T_ASSERT_EQ_INT(b->refcount, 1, NULL);

	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_EXPECT_EQ_INT(num_retains, 0, NULL);
}

static void
static_cast_move_self()
{
	BasePtr a = BasePtr::alloc();
	int a_id = a->inst_id;

	a = os::move(a).static_pointer_cast<Base>();

	T_ASSERT_NOTNULL(a.get(), "osptr seated");
	T_ASSERT_TRUE(a->inst_id == a_id, NULL);
	T_ASSERT_TRUE(a->refcount == 1, NULL);
	T_ASSERT_TRUE(num_instances == 1, NULL);
	T_ASSERT_TRUE(num_retains == 0, NULL);
}

static void
tagged_ptr()
{
	OSTaggedPtr<Base, Derived> a;
	auto b = OSTaggedPtr<Derived, Base>::alloc();

	T_ASSERT_NULL(a.get(), NULL);
	T_ASSERT_NOTNULL(b.get(), NULL);

	T_ASSERT_TRUE(typeid(a.get()) == typeid(Base *), NULL);
	T_ASSERT_TRUE(typeid(b.get()) == typeid(Derived *), NULL);
}

static void
attach()
{
	Base *a = new Base();
	BasePtr b;
	b.attach(os::move(a));

	T_ASSERT_NULL(a, NULL);
	T_ASSERT_NOTNULL(b.get(), NULL);
	T_ASSERT_EQ_INT(b->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_ASSERT_EQ_INT(num_retains, 0, NULL);

	b.attach(new Base);
	T_ASSERT_NOTNULL(b.get(), NULL);
	T_ASSERT_EQ_INT(b->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_ASSERT_EQ_INT(num_retains, 0, NULL);
	T_ASSERT_EQ_INT(num_releases, 1, NULL);
}

static void
detach()
{
	BasePtr a = BasePtr::alloc();
	Base *p = a.detach();

	T_ASSERT_NULL(a.get(), NULL);
	T_ASSERT_NOTNULL(p, NULL);
	T_ASSERT_EQ_INT(p->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);
	T_ASSERT_EQ_INT(num_retains, 0, NULL);
	T_ASSERT_EQ_INT(num_releases, 0, NULL);

	BasePtr b(os::move(p), os::no_retain); // re-seat so that 'p' gets freed
}

static void
foreign()
{
	auto a = OSPtr<Base>::alloc();
	auto b = OSTaggedPtr<Base, Derived>::alloc();

	void *a_ptr = a.get();
	void *b_ptr = b.get();

	a.swap(b);

	T_ASSERT_EQ_PTR(b.get(), a_ptr, NULL);
	T_ASSERT_EQ_PTR(a.get(), b_ptr, NULL);
	T_ASSERT_EQ_INT(a->refcount, 1, NULL);
	T_ASSERT_EQ_INT(b->refcount, 1, NULL);
	T_ASSERT_EQ_INT(num_instances, 2, NULL);
	T_ASSERT_GE_INT(num_retains, 2, NULL);
}

static void
test_dynamic_cast()
{
	auto a = DerivedPtr::alloc();
	T_ASSERT_NOTNULL(a.get(), NULL);
	BasePtr b = a;

	auto c = b.dynamic_pointer_cast<Derived>();
	T_ASSERT_NOTNULL(c.get(), NULL);

	T_ASSERT_EQ_INT(c->refcount, 3, NULL);
	T_ASSERT_EQ_INT(num_instances, 1, NULL);

	auto d = OtherPtr::alloc();
	auto e = d.dynamic_pointer_cast<Derived>();
	auto f = OSDynamicCastPtr<Derived>(OtherPtr::alloc());

	T_ASSERT_NULL(e.get(), NULL);
	T_ASSERT_NULL(f.get(), NULL);

	T_ASSERT_EQ_INT(num_instances, 2, NULL);
	T_ASSERT_EQ_INT(d->refcount, 1, NULL);

	auto g = OSDynamicCastPtr<Base>(DerivedPtr::alloc());
	T_ASSERT_EQ_INT(num_instances, 3, NULL);
	T_ASSERT_EQ_INT(g->refcount, 1, NULL);
}

#define OSPTR_TEST_DECL(name) \
	T_DECL(name, #name) { \
	        num_instances = 0; \
	        num_retains = 0; \
	        num_releases = 0; \
	        name(); \
	        T_QUIET; T_ASSERT_EQ_INT(num_instances, 0, "Instance leak"); \
	}

OSPTR_TEST_DECL(default_constructor)
OSPTR_TEST_DECL(null_constructor)
OSPTR_TEST_DECL(raw_constructor)
OSPTR_TEST_DECL(alloc)
OSPTR_TEST_DECL(destroy)
OSPTR_TEST_DECL(copy)
OSPTR_TEST_DECL(copy_subclass)
OSPTR_TEST_DECL(assign)
OSPTR_TEST_DECL(assign_raw)
OSPTR_TEST_DECL(assign_null)
OSPTR_TEST_DECL(assign_subclass)
OSPTR_TEST_DECL(assign_compatible)
OSPTR_TEST_DECL(move)
OSPTR_TEST_DECL(move_assign)
OSPTR_TEST_DECL(move_assign_null)
OSPTR_TEST_DECL(move_assign_raw)
OSPTR_TEST_DECL(move_assign_subclass)
OSPTR_TEST_DECL(move_assign_self)
OSPTR_TEST_DECL(test_const_cast)
OSPTR_TEST_DECL(const_cast_move)
OSPTR_TEST_DECL(const_cast_move_self)
OSPTR_TEST_DECL(test_static_cast)
OSPTR_TEST_DECL(static_cast_move)
OSPTR_TEST_DECL(static_cast_move_self)
OSPTR_TEST_DECL(tagged_ptr)
OSPTR_TEST_DECL(attach)
OSPTR_TEST_DECL(detach)
OSPTR_TEST_DECL(foreign)
OSPTR_TEST_DECL(test_dynamic_cast)


/*
 * Test that the "trivial_abi" attribute works as expected
 */

struct Complex {
	uintptr_t val;
	Complex() : val(71)
	{
	}
	~Complex()
	{
	}
};

struct Trivial {
	uintptr_t val;
	Trivial() : val(42)
	{
	}
	~Trivial()
	{
	}
} __attribute__((trivial_abi));

/* defined in osptr_helper.cpp */
__BEGIN_DECLS
extern uintptr_t pass_trivial(Trivial);
extern uintptr_t pass_complex(Complex);
__END_DECLS
Trivial return_trivial(uintptr_t);
Complex return_complex(uintptr_t);

T_DECL(trivial_abi, "Test trivial_abi classes are passed by value")
{
	Trivial a;
	uintptr_t x = pass_trivial(a);
	T_EXPECT_EQ_ULONG(a.val, x, "Trivial class argument passed by-value");

	Complex b;
	uintptr_t y = pass_complex(b);
	T_EXPECT_NE_ULONG(b.val, y, "Non-trivial class argument passed by-reference");

	Trivial c = return_trivial(55);
	T_EXPECT_EQ_ULONG(c.val, 55UL, "Trivial class returned by-value");

	Complex d = return_complex(99);
	T_EXPECT_NE_ULONG(d.val, 99UL, "Non-trivial class returned by-reference");
}

#pragma clang diagnostic pop