/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsQueryObject.h"
#include "mozilla/Unused.h"
#include "gtest/gtest.h"
namespace TestNsRefPtr {
#define NS_FOO_IID \
{ \
0x6f7652e0,
0xee43,
0x11d1, { \
0x9c,
0xc3,
0x00,
0x60,
0x08,
0x8c,
0xa6,
0xb3 \
} \
}
class Foo :
public nsISupports {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_FOO_IID)
public:
Foo();
// virtual dtor because Bar uses our Release()
virtual ~Foo();
NS_IMETHOD_(MozExternalRefCountType) AddRef() override;
NS_IMETHOD_(MozExternalRefCountType) Release() override;
NS_IMETHOD QueryInterface(
const nsIID&,
void**) override;
void MemberFunction(
int,
int*,
int&);
virtual void VirtualMemberFunction(
int,
int*,
int&);
virtual void VirtualConstMemberFunction(
int,
int*,
int&)
const;
void NonconstMethod() {}
void ConstMethod()
const {}
int refcount_;
static int total_constructions_;
static int total_destructions_;
static int total_addrefs_;
static int total_queries_;
};
NS_DEFINE_STATIC_IID_ACCESSOR(Foo, NS_FOO_IID)
int Foo::total_constructions_;
int Foo::total_destructions_;
int Foo::total_addrefs_;
int Foo::total_queries_;
Foo::Foo() : refcount_(
0) { ++total_constructions_; }
Foo::~Foo() { ++total_destructions_; }
MozExternalRefCountType Foo::AddRef() {
++refcount_;
++total_addrefs_;
return refcount_;
}
MozExternalRefCountType Foo::Release() {
int newcount = --refcount_;
if (newcount ==
0) {
delete this;
}
return newcount;
}
nsresult Foo::QueryInterface(
const nsIID& aIID,
void** aResult) {
++total_queries_;
nsISupports* rawPtr =
0;
nsresult status = NS_OK;
if (aIID.Equals(NS_GET_IID(Foo)))
rawPtr =
this;
else {
nsID iid_of_ISupports = NS_ISUPPORTS_IID;
if (aIID.Equals(iid_of_ISupports))
rawPtr =
static_cast<nsISupports*>(
this);
else
status = NS_ERROR_NO_INTERFACE;
}
NS_IF_ADDREF(rawPtr);
*aResult = rawPtr;
return status;
}
void Foo::MemberFunction(
int aArg1,
int* aArgPtr,
int& aArgRef) {}
void Foo::VirtualMemberFunction(
int aArg1,
int* aArgPtr,
int& aArgRef) {}
void Foo::VirtualConstMemberFunction(
int aArg1,
int* aArgPtr,
int& aArgRef)
const {}
static nsresult CreateFoo(
void** result)
// a typical factory function (that calls AddRef)
{
auto* foop =
new Foo;
foop->AddRef();
*result = foop;
return NS_OK;
}
static void set_a_Foo(RefPtr<Foo>* result) {
assert(result);
RefPtr<Foo> foop(do_QueryObject(
new Foo));
*result = foop;
}
static RefPtr<Foo> return_a_Foo() {
RefPtr<Foo> foop(do_QueryObject(
new Foo));
return foop;
}
#define NS_BAR_IID \
{ \
0x6f7652e1,
0xee43,
0x11d1, { \
0x9c,
0xc3,
0x00,
0x60,
0x08,
0x8c,
0xa6,
0xb3 \
} \
}
class Bar :
public Foo {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_BAR_IID)
public:
Bar();
~Bar() override;
NS_IMETHOD QueryInterface(
const nsIID&,
void**) override;
void VirtualMemberFunction(
int,
int*,
int&) override;
void VirtualConstMemberFunction(
int,
int*,
int&)
const override;
static int total_constructions_;
static int total_destructions_;
static int total_queries_;
};
NS_DEFINE_STATIC_IID_ACCESSOR(Bar, NS_BAR_IID)
int Bar::total_constructions_;
int Bar::total_destructions_;
int Bar::total_queries_;
Bar::Bar() { ++total_constructions_; }
Bar::~Bar() { ++total_destructions_; }
nsresult Bar::QueryInterface(
const nsID& aIID,
void** aResult) {
++total_queries_;
nsISupports* rawPtr =
0;
nsresult status = NS_OK;
if (aIID.Equals(NS_GET_IID(Bar)))
rawPtr =
this;
else if (aIID.Equals(NS_GET_IID(Foo)))
rawPtr =
static_cast<Foo*>(
this);
else {
nsID iid_of_ISupports = NS_ISUPPORTS_IID;
if (aIID.Equals(iid_of_ISupports))
rawPtr =
static_cast<nsISupports*>(
this);
else
status = NS_ERROR_NO_INTERFACE;
}
NS_IF_ADDREF(rawPtr);
*aResult = rawPtr;
return status;
}
void Bar::VirtualMemberFunction(
int aArg1,
int* aArgPtr,
int& aArgRef) {}
void Bar::VirtualConstMemberFunction(
int aArg1,
int* aArgPtr,
int& aArgRef)
const {}
}
// namespace TestNsRefPtr
using namespace TestNsRefPtr;
TEST(nsRefPtr, AddRefAndRelease)
{
Foo::total_constructions_ =
0;
Foo::total_destructions_ =
0;
{
RefPtr<Foo> foop(do_QueryObject(
new Foo));
ASSERT_EQ(Foo::total_constructions_,
1);
ASSERT_EQ(Foo::total_destructions_,
0);
ASSERT_EQ(foop->refcount_,
1);
foop = do_QueryObject(
new Foo);
ASSERT_EQ(Foo::total_constructions_,
2);
ASSERT_EQ(Foo::total_destructions_,
1);
// [Shouldn't compile] Is it a compile time error to try to |AddRef| by
// hand?
// foop->AddRef();
// [Shouldn't compile] Is it a compile time error to try to |Release| be
// hand?
// foop->Release();
// [Shouldn't compile] Is it a compile time error to try to |delete| an
// |nsCOMPtr|?
// delete foop;
static_cast<Foo*>(foop)->AddRef();
ASSERT_EQ(foop->refcount_,
2);
static_cast<Foo*>(foop)->Release();
ASSERT_EQ(foop->refcount_,
1);
}
ASSERT_EQ(Foo::total_destructions_,
2);
{
RefPtr<Foo> fooP(do_QueryObject(
new Foo));
ASSERT_EQ(Foo::total_constructions_,
3);
ASSERT_EQ(Foo::total_destructions_,
2);
ASSERT_EQ(fooP->refcount_,
1);
Foo::total_addrefs_ =
0;
RefPtr<Foo> fooP2 = std::move(fooP);
mozilla::Unused << fooP2;
ASSERT_EQ(Foo::total_addrefs_,
0);
}
}
TEST(nsRefPtr, VirtualDestructor)
{
Bar::total_destructions_ =
0;
{
RefPtr<Foo> foop(do_QueryObject(
new Bar));
mozilla::Unused << foop;
}
ASSERT_EQ(Bar::total_destructions_,
1);
}
TEST(nsRefPtr, Equality)
{
Foo::total_constructions_ =
0;
Foo::total_destructions_ =
0;
{
RefPtr<Foo> foo1p(do_QueryObject(
new Foo));
RefPtr<Foo> foo2p(do_QueryObject(
new Foo));
ASSERT_EQ(Foo::total_constructions_,
2);
ASSERT_EQ(Foo::total_destructions_,
0);
ASSERT_NE(foo1p, foo2p);
ASSERT_NE(foo1p, nullptr);
ASSERT_NE(nullptr, foo1p);
ASSERT_FALSE(foo1p == nullptr);
ASSERT_FALSE(nullptr == foo1p);
ASSERT_NE(foo1p, foo2p.get());
foo1p = foo2p;
ASSERT_EQ(Foo::total_constructions_,
2);
ASSERT_EQ(Foo::total_destructions_,
1);
ASSERT_EQ(foo1p, foo2p);
ASSERT_EQ(foo2p, foo2p.get());
ASSERT_EQ(RefPtr<Foo>(foo2p.get()), foo2p);
ASSERT_TRUE(foo1p);
}
ASSERT_EQ(Foo::total_constructions_,
2);
ASSERT_EQ(Foo::total_destructions_,
2);
}
TEST(nsRefPtr, AddRefHelpers)
{
Foo::total_addrefs_ =
0;
{
auto* raw_foo1p =
new Foo;
raw_foo1p->AddRef();
auto* raw_foo2p =
new Foo;
raw_foo2p->AddRef();
ASSERT_EQ(Foo::total_addrefs_,
2);
RefPtr<Foo> foo1p(dont_AddRef(raw_foo1p));
ASSERT_EQ(Foo::total_addrefs_,
2);
RefPtr<Foo> foo2p;
foo2p = dont_AddRef(raw_foo2p);
ASSERT_EQ(Foo::total_addrefs_,
2);
}
{
// Test that various assignment helpers compile.
RefPtr<Foo> foop;
CreateFoo(RefPtrGetterAddRefs<Foo>(foop));
CreateFoo(getter_AddRefs(foop));
set_a_Foo(address_of(foop));
foop = return_a_Foo();
}
}
TEST(nsRefPtr, QueryInterface)
{
Foo::total_queries_ =
0;
Bar::total_queries_ =
0;
{
RefPtr<Foo> fooP;
fooP = do_QueryObject(
new Foo);
ASSERT_EQ(Foo::total_queries_,
1);
}
{
RefPtr<Foo> fooP;
fooP = do_QueryObject(
new Foo);
ASSERT_EQ(Foo::total_queries_,
2);
RefPtr<Foo> foo2P;
foo2P = fooP;
ASSERT_EQ(Foo::total_queries_,
2);
}
{
RefPtr<Bar> barP(do_QueryObject(
new Bar));
ASSERT_EQ(Bar::total_queries_,
1);
RefPtr<Foo> fooP(do_QueryObject(barP));
ASSERT_TRUE(fooP);
ASSERT_EQ(Foo::total_queries_,
2);
ASSERT_EQ(Bar::total_queries_,
2);
}
}
// -------------------------------------------------------------------------
// TODO(ER): The following tests should be moved to MFBT.
#define NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(_
class) \
public: \
NS_METHOD_(MozExternalRefCountType) AddRef(
void)
const { \
MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_
class) \
MOZ_RELEASE_ASSERT(int32_t(mRefCnt) >=
0,
"illegal refcnt"); \
nsrefcnt count = ++mRefCnt; \
return (nsrefcnt)count; \
} \
NS_METHOD_(MozExternalRefCountType) Release(
void)
const { \
MOZ_RELEASE_ASSERT(int32_t(mRefCnt) >
0,
"dup release"); \
nsrefcnt count = --mRefCnt; \
if (count ==
0) { \
delete (
this); \
return 0; \
} \
return count; \
} \
\
protected: \
mutable ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \
\
public:
class ObjectForConstPtr {
private:
// Reference-counted classes cannot have public destructors.
~ObjectForConstPtr() =
default;
public:
NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(ObjectForConstPtr)
void ConstMemberFunction(
int aArg1,
int* aArgPtr,
int& aArgRef)
const {}
};
#undef NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING
namespace TestNsRefPtr {
static void AnFooPtrPtrContext(Foo**) {}
static void AVoidPtrPtrContext(
void**) {}
}
// namespace TestNsRefPtr
TEST(nsRefPtr, RefPtrCompilationTests)
{
{
RefPtr<Foo> fooP;
AnFooPtrPtrContext(getter_AddRefs(fooP));
AVoidPtrPtrContext(getter_AddRefs(fooP));
}
{
RefPtr<Foo> fooP(
new Foo);
RefPtr<
const Foo> constFooP = fooP;
constFooP->ConstMethod();
// [Shouldn't compile] Is it a compile time error to call a non-const method
// on an |RefPtr<const T>|?
// constFooP->NonconstMethod();
// [Shouldn't compile] Is it a compile time error to construct an |RefPtr<T>
// from an |RefPtr<const T>|?
// RefPtr<Foo> otherFooP(constFooP);
}
{
RefPtr<Foo> foop =
new Foo;
RefPtr<Foo> foop2 =
new Bar;
RefPtr<
const ObjectForConstPtr> foop3 =
new ObjectForConstPtr;
int test =
1;
void (Foo::*fPtr)(
int,
int*,
int&) = &Foo::MemberFunction;
void (Foo::*fVPtr)(
int,
int*,
int&) = &Foo::VirtualMemberFunction;
void (Foo::*fVCPtr)(
int,
int*,
int&)
const =
&Foo::VirtualConstMemberFunction;
void (ObjectForConstPtr::*fCPtr2)(
int,
int*,
int&)
const =
&ObjectForConstPtr::ConstMemberFunction;
(foop->*fPtr)(test, &test, test);
(foop2->*fVPtr)(test, &test, test);
(foop2->*fVCPtr)(test, &test, test);
(foop3->*fCPtr2)(test, &test, test);
}
// Looks like everything ran.
ASSERT_TRUE(
true);
}