/* -*- 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(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* result) { assert(result); RefPtr foop(do_QueryObject(new Foo)); *result = foop; } static RefPtr return_a_Foo() { RefPtr 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(this); else { nsID iid_of_ISupports = NS_ISUPPORTS_IID; if (aIID.Equals(iid_of_ISupports)) rawPtr = static_cast(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 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(foop)->AddRef(); ASSERT_EQ(foop->refcount_, 2); static_cast(foop)->Release(); ASSERT_EQ(foop->refcount_, 1); } ASSERT_EQ(Foo::total_destructions_, 2); { RefPtr 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 fooP2 = std::move(fooP); mozilla::Unused << fooP2; ASSERT_EQ(Foo::total_addrefs_, 0); } } TEST(nsRefPtr, VirtualDestructor) { Bar::total_destructions_ = 0; { RefPtr 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 foo1p(do_QueryObject(new Foo)); RefPtr 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(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 foo1p(dont_AddRef(raw_foo1p)); ASSERT_EQ(Foo::total_addrefs_, 2); RefPtr foo2p; foo2p = dont_AddRef(raw_foo2p); ASSERT_EQ(Foo::total_addrefs_, 2); } { // Test that various assignment helpers compile. RefPtr foop; CreateFoo(RefPtrGetterAddRefs(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 fooP; fooP = do_QueryObject(new Foo); ASSERT_EQ(Foo::total_queries_, 1); } { RefPtr fooP; fooP = do_QueryObject(new Foo); ASSERT_EQ(Foo::total_queries_, 2); RefPtr foo2P; foo2P = fooP; ASSERT_EQ(Foo::total_queries_, 2); } { RefPtr barP(do_QueryObject(new Bar)); ASSERT_EQ(Bar::total_queries_, 1); RefPtr 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 fooP; AnFooPtrPtrContext(getter_AddRefs(fooP)); AVoidPtrPtrContext(getter_AddRefs(fooP)); } { RefPtr fooP(new Foo); RefPtr constFooP = fooP; constFooP->ConstMethod(); // [Shouldn't compile] Is it a compile time error to call a non-const method // on an |RefPtr|? // constFooP->NonconstMethod(); // [Shouldn't compile] Is it a compile time error to construct an |RefPtr // from an |RefPtr|? // RefPtr otherFooP(constFooP); } { RefPtr foop = new Foo; RefPtr foop2 = new Bar; RefPtr 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); }