blob: cda149a33a8fa481418cd3fcf21f149148f0f797 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file tests both ref_counted.h and ref_ptr.h (which the former includes).
// TODO(vtl): Possibly we could separate these tests out better, since a lot of
// it is actually testing |RefPtr|.
#include "flutter/fml/memory/ref_counted.h"
#include "flutter/fml/macros.h"
#include "gtest/gtest.h"
#if defined(__clang__)
#define ALLOW_PESSIMIZING_MOVE(code_line) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wpessimizing-move\"") code_line; \
_Pragma("clang diagnostic pop")
#else
#define ALLOW_PESSIMIZING_MOVE(code_line) code_line;
#endif
#if defined(__clang__)
#define ALLOW_SELF_MOVE(code_line) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wself-move\"") code_line; \
_Pragma("clang diagnostic pop")
#else
#define ALLOW_SELF_MOVE(code_line) code_line;
#endif
#if defined(__clang__)
#define ALLOW_SELF_ASSIGN_OVERLOADED(code_line) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wself-assign-overloaded\"") \
code_line; \
_Pragma("clang diagnostic pop")
#else
#define ALLOW_SELF_ASSIGN_OVERLOADED(code_line) code_line;
#endif
namespace fml {
namespace {
class MyClass : public RefCountedThreadSafe<MyClass> {
protected:
MyClass(MyClass** created, bool* was_destroyed)
: was_destroyed_(was_destroyed) {
if (created) {
*created = this;
}
}
virtual ~MyClass() {
if (was_destroyed_) {
*was_destroyed_ = true;
}
}
private:
FML_FRIEND_REF_COUNTED_THREAD_SAFE(MyClass);
FML_FRIEND_MAKE_REF_COUNTED(MyClass);
bool* was_destroyed_;
FML_DISALLOW_COPY_AND_ASSIGN(MyClass);
};
class MySubclass final : public MyClass {
private:
FML_FRIEND_REF_COUNTED_THREAD_SAFE(MySubclass);
FML_FRIEND_MAKE_REF_COUNTED(MySubclass);
MySubclass(MySubclass** created, bool* was_destroyed)
: MyClass(nullptr, was_destroyed) {
if (created) {
*created = this;
}
}
~MySubclass() override {}
FML_DISALLOW_COPY_AND_ASSIGN(MySubclass);
};
TEST(RefCountedTest, Constructors) {
bool was_destroyed;
{
// Default.
RefPtr<MyClass> r;
EXPECT_TRUE(r.get() == nullptr);
EXPECT_FALSE(r);
}
{
// Nullptr.
RefPtr<MyClass> r(nullptr);
EXPECT_TRUE(r.get() == nullptr);
EXPECT_FALSE(r);
}
{
MyClass* created = nullptr;
was_destroyed = false;
// Adopt, then RVO.
RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
EXPECT_TRUE(created);
EXPECT_EQ(created, r.get());
EXPECT_TRUE(r);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MyClass* created = nullptr;
was_destroyed = false;
// Adopt, then move.
ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r(
std::move(MakeRefCounted<MyClass>(&created, &was_destroyed))))
EXPECT_TRUE(created);
EXPECT_EQ(created, r.get());
EXPECT_TRUE(r);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MyClass* created = nullptr;
was_destroyed = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
// Copy.
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
RefPtr<MyClass> r2(r1);
EXPECT_TRUE(created);
EXPECT_EQ(created, r1.get());
EXPECT_EQ(created, r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MyClass* created = nullptr;
was_destroyed = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
// From raw pointer.
RefPtr<MyClass> r2(created);
EXPECT_TRUE(created);
EXPECT_EQ(created, r1.get());
EXPECT_EQ(created, r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MySubclass* created = nullptr;
was_destroyed = false;
// Adopt, then "move".
RefPtr<MyClass> r(MakeRefCounted<MySubclass>(&created, &was_destroyed));
EXPECT_TRUE(created);
EXPECT_EQ(static_cast<MyClass*>(created), r.get());
EXPECT_TRUE(r);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MySubclass* created = nullptr;
was_destroyed = false;
// Adopt, then "move".
ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r(
std::move(MakeRefCounted<MySubclass>(&created, &was_destroyed))))
EXPECT_TRUE(created);
EXPECT_EQ(static_cast<MyClass*>(created), r.get());
EXPECT_TRUE(r);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MySubclass* created = nullptr;
was_destroyed = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
// "Copy".
RefPtr<MyClass> r2(r1);
EXPECT_TRUE(created);
EXPECT_EQ(static_cast<MyClass*>(created), r1.get());
EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MySubclass* created = nullptr;
was_destroyed = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
// From raw pointer.
RefPtr<MyClass> r2(created);
EXPECT_TRUE(created);
EXPECT_EQ(static_cast<MyClass*>(created), r1.get());
EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
}
TEST(RefCountedTest, NullAssignmentToNull) {
RefPtr<MyClass> r1;
// No-op null assignment using |nullptr|.
r1 = nullptr;
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_FALSE(r1);
RefPtr<MyClass> r2;
// No-op null assignment using copy constructor.
r1 = r2;
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_TRUE(r2.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_FALSE(r2);
// No-op null assignment using move constructor.
r1 = std::move(r2);
EXPECT_TRUE(r1.get() == nullptr);
// The clang linter flags the method called on the moved-from reference, but
// this is testing the move implementation, so it is marked NOLINT.
EXPECT_TRUE(r2.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move,
// bugprone-use-after-move)
EXPECT_FALSE(r1);
EXPECT_FALSE(r2);
RefPtr<MySubclass> r3;
// No-op null assignment using "copy" constructor.
r1 = r3;
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_TRUE(r3.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_FALSE(r3);
// No-op null assignment using "move" constructor.
r1 = std::move(r3);
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_TRUE(r3.get() == nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_FALSE(r1);
EXPECT_FALSE(r3);
}
TEST(RefCountedTest, NonNullAssignmentToNull) {
bool was_destroyed;
{
MyClass* created = nullptr;
was_destroyed = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
RefPtr<MyClass> r2;
// Copy assignment (to null ref pointer).
r2 = r1;
EXPECT_EQ(created, r1.get());
EXPECT_EQ(created, r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MyClass* created = nullptr;
was_destroyed = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
RefPtr<MyClass> r2;
// Move assignment (to null ref pointer).
r2 = std::move(r1);
// The clang linter flags the method called on the moved-from reference, but
// this is testing the move implementation, so it is marked NOLINT.
EXPECT_TRUE(r1.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move,
// bugprone-use-after-move)
EXPECT_EQ(created, r2.get());
EXPECT_FALSE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MySubclass* created = nullptr;
was_destroyed = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
RefPtr<MyClass> r2;
// "Copy" assignment (to null ref pointer).
r2 = r1;
EXPECT_EQ(created, r1.get());
EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MySubclass* created = nullptr;
was_destroyed = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
RefPtr<MyClass> r2;
// "Move" assignment (to null ref pointer).
r2 = std::move(r1);
EXPECT_TRUE(r1.get() == nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
EXPECT_FALSE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
}
TEST(RefCountedTest, NullAssignmentToNonNull) {
bool was_destroyed = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed));
// Null assignment (to non-null ref pointer) using |nullptr|.
r1 = nullptr;
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_TRUE(was_destroyed);
was_destroyed = false;
r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
RefPtr<MyClass> r2;
// Null assignment (to non-null ref pointer) using copy constructor.
r1 = r2;
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_TRUE(r2.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_FALSE(r2);
EXPECT_TRUE(was_destroyed);
was_destroyed = false;
r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
// Null assignment using move constructor.
r1 = std::move(r2);
EXPECT_TRUE(r1.get() == nullptr);
// The clang linter flags the method called on the moved-from reference, but
// this is testing the move implementation, so it is marked NOLINT.
EXPECT_TRUE(r2.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move,
// bugprone-use-after-move)
EXPECT_FALSE(r1);
EXPECT_FALSE(r2);
EXPECT_TRUE(was_destroyed);
was_destroyed = false;
r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
RefPtr<MySubclass> r3;
// Null assignment (to non-null ref pointer) using "copy" constructor.
r1 = r3;
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_TRUE(r3.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_FALSE(r3);
EXPECT_TRUE(was_destroyed);
was_destroyed = false;
r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
// Null assignment (to non-null ref pointer) using "move" constructor.
r1 = std::move(r3);
EXPECT_TRUE(r1.get() == nullptr);
EXPECT_TRUE(r3.get() == nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_FALSE(r1);
EXPECT_FALSE(r3);
EXPECT_TRUE(was_destroyed);
}
TEST(RefCountedTest, NonNullAssignmentToNonNull) {
bool was_destroyed1;
bool was_destroyed2;
{
was_destroyed1 = false;
was_destroyed2 = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1));
RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
// Copy assignment (to non-null ref pointer).
r2 = r1;
EXPECT_EQ(r1.get(), r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed1);
EXPECT_TRUE(was_destroyed2);
}
EXPECT_TRUE(was_destroyed1);
{
was_destroyed1 = false;
was_destroyed2 = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1));
RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
// Move assignment (to non-null ref pointer).
r2 = std::move(r1);
// The clang linter flags the method called on the moved-from reference, but
// this is testing the move implementation, so it is marked NOLINT.
EXPECT_TRUE(r1.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move,
// bugprone-use-after-move)
EXPECT_FALSE(r2.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed1);
EXPECT_TRUE(was_destroyed2);
}
EXPECT_TRUE(was_destroyed1);
{
was_destroyed1 = false;
was_destroyed2 = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1));
RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
// "Copy" assignment (to non-null ref pointer).
r2 = r1;
EXPECT_EQ(r1.get(), r2.get());
EXPECT_TRUE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed1);
EXPECT_TRUE(was_destroyed2);
}
EXPECT_TRUE(was_destroyed1);
{
was_destroyed1 = false;
was_destroyed2 = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1));
RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
// Move assignment (to non-null ref pointer).
r2 = std::move(r1);
EXPECT_TRUE(r1.get() == nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_FALSE(r2.get() == nullptr);
EXPECT_FALSE(r1);
EXPECT_TRUE(r2);
EXPECT_FALSE(was_destroyed1);
EXPECT_TRUE(was_destroyed2);
}
EXPECT_TRUE(was_destroyed1);
}
TEST(RefCountedTest, SelfAssignment) {
bool was_destroyed;
{
MyClass* created = nullptr;
was_destroyed = false;
// This line is marked NOLINT because the clang linter does not reason about
// the value of the reference count. In particular, the self-assignment
// below is handled in the copy constructor by a refcount increment then
// decrement. The linter sees only that the decrement might destroy the
// object.
RefPtr<MyClass> r(MakeRefCounted<MyClass>( // NOLINT
&created, &was_destroyed));
// Copy.
ALLOW_SELF_ASSIGN_OVERLOADED(r = r);
EXPECT_EQ(created, r.get());
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
{
MyClass* created = nullptr;
was_destroyed = false;
RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
// Move.
ALLOW_SELF_MOVE(r = std::move(r))
EXPECT_EQ(created, r.get());
EXPECT_FALSE(was_destroyed);
}
EXPECT_TRUE(was_destroyed);
}
TEST(RefCountedTest, Swap) {
MyClass* created1 = nullptr;
static bool was_destroyed1;
was_destroyed1 = false;
RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created1, &was_destroyed1));
EXPECT_TRUE(created1);
EXPECT_EQ(created1, r1.get());
MyClass* created2 = nullptr;
static bool was_destroyed2;
was_destroyed2 = false;
RefPtr<MyClass> r2(MakeRefCounted<MyClass>(&created2, &was_destroyed2));
EXPECT_TRUE(created2);
EXPECT_EQ(created2, r2.get());
EXPECT_NE(created1, created2);
r1.swap(r2);
EXPECT_EQ(created2, r1.get());
EXPECT_EQ(created1, r2.get());
}
TEST(RefCountedTest, GetAndDereferenceOperators) {
// Note: We check here that .get(), operator*, and operator-> are const, but
// return non-const pointers/refs.
MyClass* created = nullptr;
const RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, nullptr));
MyClass* ptr = r.get(); // Assign to non-const pointer.
EXPECT_EQ(created, ptr);
ptr = r.operator->(); // Assign to non-const pointer.
EXPECT_EQ(created, ptr);
MyClass& ref = *r; // "Assign" to non-const reference.
EXPECT_EQ(created, &ref);
}
// You can manually call |AddRef()| and |Release()| if you want.
TEST(RefCountedTest, AddRefRelease) {
MyClass* created = nullptr;
bool was_destroyed = false;
{
RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
EXPECT_EQ(created, r.get());
created->AddRef();
}
EXPECT_FALSE(was_destroyed);
created->Release();
EXPECT_TRUE(was_destroyed);
}
TEST(RefCountedTest, Mix) {
MySubclass* created = nullptr;
bool was_destroyed = false;
RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
ASSERT_FALSE(was_destroyed);
EXPECT_TRUE(created->HasOneRef());
created->AssertHasOneRef();
RefPtr<MySubclass> r2 = r1;
ASSERT_FALSE(was_destroyed);
EXPECT_FALSE(created->HasOneRef());
r1 = nullptr;
ASSERT_FALSE(was_destroyed);
created->AssertHasOneRef();
{
RefPtr<MyClass> r3 = r2;
EXPECT_FALSE(created->HasOneRef());
{
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
RefPtr<MyClass> r4(r3);
r2 = nullptr;
ASSERT_FALSE(was_destroyed);
EXPECT_FALSE(created->HasOneRef());
}
ASSERT_FALSE(was_destroyed);
EXPECT_TRUE(created->HasOneRef());
created->AssertHasOneRef();
r1 = RefPtr<MySubclass>(static_cast<MySubclass*>(r3.get()));
ASSERT_FALSE(was_destroyed);
EXPECT_FALSE(created->HasOneRef());
}
ASSERT_FALSE(was_destroyed);
EXPECT_TRUE(created->HasOneRef());
created->AssertHasOneRef();
EXPECT_EQ(created, r1.get());
r1 = nullptr;
EXPECT_TRUE(was_destroyed);
}
class MyPublicClass : public RefCountedThreadSafe<MyPublicClass> {
public:
// Overloaded constructors work with |MakeRefCounted()|.
MyPublicClass() : has_num_(false), num_(0) {}
explicit MyPublicClass(int num) : has_num_(true), num_(num) {}
~MyPublicClass() {}
bool has_num() const { return has_num_; }
int num() const { return num_; }
private:
bool has_num_;
int num_;
FML_DISALLOW_COPY_AND_ASSIGN(MyPublicClass);
};
// You can also just keep constructors and destructors public. Make sure that
// works (mostly that it compiles).
TEST(RefCountedTest, PublicCtorAndDtor) {
RefPtr<MyPublicClass> r1 = MakeRefCounted<MyPublicClass>();
ASSERT_TRUE(r1);
EXPECT_FALSE(r1->has_num());
RefPtr<MyPublicClass> r2 = MakeRefCounted<MyPublicClass>(123);
ASSERT_TRUE(r2);
EXPECT_TRUE(r2->has_num());
EXPECT_EQ(123, r2->num());
EXPECT_NE(r1.get(), r2.get());
r1 = r2;
EXPECT_TRUE(r1->has_num());
EXPECT_EQ(123, r1->num());
EXPECT_EQ(r1.get(), r2.get());
r2 = nullptr;
EXPECT_FALSE(r2);
EXPECT_TRUE(r1->has_num());
EXPECT_EQ(123, r1->num());
r1 = nullptr;
EXPECT_FALSE(r1);
}
// The danger with having a public constructor or destructor is that certain
// things will compile. You should get some protection by assertions in Debug
// builds.
#ifndef NDEBUG
TEST(RefCountedTest, DebugChecks) {
{
MyPublicClass* p = new MyPublicClass();
EXPECT_DEATH_IF_SUPPORTED( // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
delete p, "!adoption_required_");
}
{
MyPublicClass* p = new MyPublicClass();
EXPECT_DEATH_IF_SUPPORTED( // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
RefPtr<MyPublicClass> r(p), "!adoption_required_");
}
{
RefPtr<MyPublicClass> r(MakeRefCounted<MyPublicClass>());
EXPECT_DEATH_IF_SUPPORTED(delete r.get(), "destruction_started_");
}
}
#endif
// TODO(vtl): Add (threaded) stress tests.
} // namespace
} // namespace fml