/* -*- 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(); NS_IMETHOD_(MozExternalRefCountType) Release(); NS_IMETHOD QueryInterface( const nsIID&, void** ); 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 { } nsresult CreateFoo( void** result ) // a typical factory function (that calls AddRef) { Foo* foop = new Foo; foop->AddRef(); *result = foop; return NS_OK; } void set_a_Foo( RefPtr* result ) { assert(result); RefPtr foop( do_QueryObject(new Foo) ); *result = foop; } 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(); virtual ~Bar(); NS_IMETHOD QueryInterface( const nsIID&, void** ) override; virtual void VirtualMemberFunction( int, int*, int& ) override; virtual 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( fooP.forget() ); 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; { Foo* raw_foo1p = new Foo; raw_foo1p->AddRef(); Foo* 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() { } 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 { void AnFooPtrPtrContext(Foo**) { } 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); }