blob: 4e41f6ef8145270f3b889da58ce135a46d03323d [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/observer_list.h"
#include "base/strings/string_piece.h"
#include "base/test/gtest_util.h"
#include "base/threading/simple_thread.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
class CheckedBase : public CheckedObserver {
public:
virtual void Observe(int x) = 0;
~CheckedBase() override = default;
virtual int GetValue() const { return 0; }
};
class UncheckedBase {
public:
virtual void Observe(int x) = 0;
virtual ~UncheckedBase() = default;
virtual int GetValue() const { return 0; }
};
// Helper for TYPED_TEST_SUITE machinery to pick the ObserverList under test.
// Keyed off the observer type since ObserverList has too many template args and
// it gets ugly.
template <class Foo>
struct PickObserverList {};
template <>
struct PickObserverList<CheckedBase> {
template <class TypeParam,
bool check_empty = false,
bool allow_reentrancy = true>
using ObserverListType =
ObserverList<TypeParam, check_empty, allow_reentrancy>;
};
template <>
struct PickObserverList<UncheckedBase> {
template <class TypeParam,
bool check_empty = false,
bool allow_reentrancy = true>
using ObserverListType = typename ObserverList<TypeParam,
check_empty,
allow_reentrancy>::Unchecked;
};
template <class Foo>
class AdderT : public Foo {
public:
explicit AdderT(int scaler) : total(0), scaler_(scaler) {}
~AdderT() override = default;
void Observe(int x) override { total += x * scaler_; }
int GetValue() const override { return total; }
int total;
private:
int scaler_;
};
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class DisrupterT : public Foo {
public:
DisrupterT(ObserverListType* list, Foo* doomed, bool remove_self)
: list_(list), doomed_(doomed), remove_self_(remove_self) {}
DisrupterT(ObserverListType* list, Foo* doomed)
: DisrupterT(list, doomed, false) {}
DisrupterT(ObserverListType* list, bool remove_self)
: DisrupterT(list, nullptr, remove_self) {}
~DisrupterT() override = default;
void Observe(int x) override {
if (remove_self_)
list_->RemoveObserver(this);
if (doomed_)
list_->RemoveObserver(doomed_);
}
void SetDoomed(Foo* doomed) { doomed_ = doomed; }
private:
ObserverListType* list_;
Foo* doomed_;
bool remove_self_;
};
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class AddInObserve : public Foo {
public:
explicit AddInObserve(ObserverListType* observer_list)
: observer_list(observer_list), to_add_() {}
void SetToAdd(Foo* to_add) { to_add_ = to_add; }
void Observe(int x) override {
if (to_add_) {
observer_list->AddObserver(to_add_);
to_add_ = nullptr;
}
}
ObserverListType* observer_list;
Foo* to_add_;
};
template <class ObserverListType>
class ObserverListCreator : public DelegateSimpleThread::Delegate {
public:
std::unique_ptr<ObserverListType> Create(
base::Optional<base::ObserverListPolicy> policy = nullopt) {
policy_ = policy;
DelegateSimpleThread thread(this, "ListCreator");
thread.Start();
thread.Join();
return std::move(observer_list_);
}
private:
void Run() override {
if (policy_) {
observer_list_ = std::make_unique<ObserverListType>(*policy_);
} else {
observer_list_ = std::make_unique<ObserverListType>();
}
}
std::unique_ptr<ObserverListType> observer_list_;
base::Optional<base::ObserverListPolicy> policy_;
};
} // namespace
class ObserverListTestBase {
public:
ObserverListTestBase() {}
template <class T>
const decltype(T::list_.get()) list(const T& iter) {
return iter.list_.get();
}
template <class T>
typename T::value_type* GetCurrent(T* iter) {
return iter->GetCurrent();
}
// Override GetCurrent() for CheckedObserver. When StdIteratorRemoveFront
// tries to simulate a sequence to see if it "would" crash, CheckedObservers
// do, actually, crash with a DCHECK(). Note this check is different to the
// check during an observer _iteration_. Hence, DCHECK(), not CHECK().
CheckedBase* GetCurrent(ObserverList<CheckedBase>::iterator* iter) {
EXPECT_DCHECK_DEATH(return iter->GetCurrent());
return nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(ObserverListTestBase);
};
// Templatized test fixture that can pick between CheckedBase and UncheckedBase.
template <class ObserverType>
class ObserverListTest : public ObserverListTestBase, public ::testing::Test {
public:
template <class T>
using ObserverList =
typename PickObserverList<ObserverType>::template ObserverListType<T>;
using iterator = typename ObserverList<ObserverType>::iterator;
using const_iterator = typename ObserverList<ObserverType>::const_iterator;
ObserverListTest() {}
private:
DISALLOW_COPY_AND_ASSIGN(ObserverListTest);
};
using ObserverTypes = ::testing::Types<CheckedBase, UncheckedBase>;
TYPED_TEST_SUITE(ObserverListTest, ObserverTypes);
// TYPED_TEST causes the test parent class to be a template parameter, which
// makes the syntax for referring to the types awkward. Create aliases in local
// scope with clearer names. Unfortunately, we also need some trailing cruft to
// avoid "unused local type alias" warnings.
#define DECLARE_TYPES \
using Foo = TypeParam; \
using ObserverListFoo = \
typename PickObserverList<TypeParam>::template ObserverListType<Foo>; \
using Adder = AdderT<Foo>; \
using Disrupter = DisrupterT<ObserverListFoo>; \
using const_iterator = typename TestFixture::const_iterator; \
using iterator = typename TestFixture::iterator; \
(void)(Disrupter*)(0); \
(void)(Adder*)(0); \
(void)(const_iterator*)(0); \
(void)(iterator*)(0)
TYPED_TEST(ObserverListTest, BasicTest) {
DECLARE_TYPES;
ObserverListFoo observer_list;
const ObserverListFoo& const_observer_list = observer_list;
{
const const_iterator it1 = const_observer_list.begin();
EXPECT_EQ(it1, const_observer_list.end());
// Iterator copy.
const const_iterator it2 = it1;
EXPECT_EQ(it2, it1);
// Iterator assignment.
const_iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Self assignment.
it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
}
{
const iterator it1 = observer_list.begin();
EXPECT_EQ(it1, observer_list.end());
// Iterator copy.
const iterator it2 = it1;
EXPECT_EQ(it2, it1);
// Iterator assignment.
iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Self assignment.
it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
}
Adder a(1), b(-1), c(1), d(-1), e(-1);
Disrupter evil(&observer_list, &c);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
EXPECT_TRUE(const_observer_list.HasObserver(&a));
EXPECT_FALSE(const_observer_list.HasObserver(&c));
{
const const_iterator it1 = const_observer_list.begin();
EXPECT_NE(it1, const_observer_list.end());
// Iterator copy.
const const_iterator it2 = it1;
EXPECT_EQ(it2, it1);
EXPECT_NE(it2, const_observer_list.end());
// Iterator assignment.
const_iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Self assignment.
it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Iterator post increment.
const_iterator it4 = it3++;
EXPECT_EQ(it4, it1);
EXPECT_EQ(it4, it2);
EXPECT_NE(it4, it3);
}
{
const iterator it1 = observer_list.begin();
EXPECT_NE(it1, observer_list.end());
// Iterator copy.
const iterator it2 = it1;
EXPECT_EQ(it2, it1);
EXPECT_NE(it2, observer_list.end());
// Iterator assignment.
iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Self assignment.
it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Iterator post increment.
iterator it4 = it3++;
EXPECT_EQ(it4, it1);
EXPECT_EQ(it4, it2);
EXPECT_NE(it4, it3);
}
for (auto& observer : observer_list)
observer.Observe(10);
observer_list.AddObserver(&evil);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
// Removing an observer not in the list should do nothing.
observer_list.RemoveObserver(&e);
for (auto& observer : observer_list)
observer.Observe(10);
EXPECT_EQ(20, a.total);
EXPECT_EQ(-20, b.total);
EXPECT_EQ(0, c.total);
EXPECT_EQ(-10, d.total);
EXPECT_EQ(0, e.total);
}
TYPED_TEST(ObserverListTest, CreatedAndUsedOnDifferentThreads) {
DECLARE_TYPES;
ObserverListCreator<ObserverListFoo> list_creator;
Adder a(1);
// Check with default constructor
{
std::unique_ptr<ObserverListFoo> observer_list = list_creator.Create();
observer_list->AddObserver(&a);
for (auto& observer : *observer_list) {
observer.Observe(1);
}
EXPECT_EQ(1, a.GetValue());
}
// Check with constructor taking explicit policy
{
std::unique_ptr<ObserverListFoo> observer_list =
list_creator.Create(base::ObserverListPolicy::EXISTING_ONLY);
observer_list->AddObserver(&a);
for (auto& observer : *observer_list) {
observer.Observe(1);
}
EXPECT_EQ(2, a.GetValue());
}
}
TYPED_TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
DECLARE_TYPES;
using ObserverListConstFoo =
typename TestFixture::template ObserverList<const Foo>;
ObserverListConstFoo ol;
const ObserverListConstFoo& col = ol;
const Adder a(1);
const Adder b(2);
const Adder c(3);
ol.AddObserver(&a);
ol.AddObserver(&b);
EXPECT_TRUE(col.HasObserver(&a));
EXPECT_FALSE(col.HasObserver(&c));
EXPECT_TRUE(col.might_have_observers());
using It = typename ObserverListConstFoo::const_iterator;
{
It it = col.begin();
EXPECT_NE(it, col.end());
It ita = it;
EXPECT_EQ(ita, it);
EXPECT_NE(++it, col.end());
EXPECT_NE(ita, it);
It itb = it;
EXPECT_EQ(itb, it);
EXPECT_EQ(++it, col.end());
EXPECT_TRUE(col.might_have_observers());
EXPECT_EQ(&*ita, &a);
EXPECT_EQ(&*itb, &b);
ol.RemoveObserver(&a);
EXPECT_TRUE(col.might_have_observers());
EXPECT_FALSE(col.HasObserver(&a));
EXPECT_EQ(&*itb, &b);
ol.RemoveObserver(&b);
EXPECT_TRUE(col.might_have_observers());
EXPECT_FALSE(col.HasObserver(&a));
EXPECT_FALSE(col.HasObserver(&b));
it = It();
ita = It();
EXPECT_TRUE(col.might_have_observers());
ita = itb;
itb = It();
EXPECT_TRUE(col.might_have_observers());
ita = It();
EXPECT_FALSE(col.might_have_observers());
}
ol.AddObserver(&a);
ol.AddObserver(&b);
EXPECT_TRUE(col.might_have_observers());
ol.Clear();
EXPECT_FALSE(col.might_have_observers());
ol.AddObserver(&a);
ol.AddObserver(&b);
EXPECT_TRUE(col.might_have_observers());
{
const It it = col.begin();
ol.Clear();
EXPECT_TRUE(col.might_have_observers());
}
EXPECT_FALSE(col.might_have_observers());
}
TYPED_TEST(ObserverListTest, DisruptSelf) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter evil(&observer_list, true);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
for (auto& observer : observer_list)
observer.Observe(10);
observer_list.AddObserver(&evil);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& observer : observer_list)
observer.Observe(10);
EXPECT_EQ(20, a.total);
EXPECT_EQ(-20, b.total);
EXPECT_EQ(10, c.total);
EXPECT_EQ(-10, d.total);
}
TYPED_TEST(ObserverListTest, DisruptBefore) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter evil(&observer_list, &b);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&evil);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& observer : observer_list)
observer.Observe(10);
for (auto& observer : observer_list)
observer.Observe(10);
EXPECT_EQ(20, a.total);
EXPECT_EQ(-10, b.total);
EXPECT_EQ(20, c.total);
EXPECT_EQ(-20, d.total);
}
TYPED_TEST(ObserverListTest, Existing) {
DECLARE_TYPES;
ObserverListFoo observer_list(ObserverListPolicy::EXISTING_ONLY);
Adder a(1);
AddInObserve<ObserverListFoo> b(&observer_list);
Adder c(1);
b.SetToAdd(&c);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
for (auto& observer : observer_list)
observer.Observe(1);
EXPECT_FALSE(b.to_add_);
// B's adder should not have been notified because it was added during
// notification.
EXPECT_EQ(0, c.total);
// Notify again to make sure b's adder is notified.
for (auto& observer : observer_list)
observer.Observe(1);
EXPECT_EQ(1, c.total);
}
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class AddInClearObserve : public Foo {
public:
explicit AddInClearObserve(ObserverListType* list)
: list_(list), added_(false), adder_(1) {}
void Observe(int /* x */) override {
list_->Clear();
list_->AddObserver(&adder_);
added_ = true;
}
bool added() const { return added_; }
const AdderT<Foo>& adder() const { return adder_; }
private:
ObserverListType* const list_;
bool added_;
AdderT<Foo> adder_;
};
TYPED_TEST(ObserverListTest, ClearNotifyAll) {
DECLARE_TYPES;
ObserverListFoo observer_list;
AddInClearObserve<ObserverListFoo> a(&observer_list);
observer_list.AddObserver(&a);
for (auto& observer : observer_list)
observer.Observe(1);
EXPECT_TRUE(a.added());
EXPECT_EQ(1, a.adder().total)
<< "Adder should observe once and have sum of 1.";
}
TYPED_TEST(ObserverListTest, ClearNotifyExistingOnly) {
DECLARE_TYPES;
ObserverListFoo observer_list(ObserverListPolicy::EXISTING_ONLY);
AddInClearObserve<ObserverListFoo> a(&observer_list);
observer_list.AddObserver(&a);
for (auto& observer : observer_list)
observer.Observe(1);
EXPECT_TRUE(a.added());
EXPECT_EQ(0, a.adder().total)
<< "Adder should not observe, so sum should still be 0.";
}
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class ListDestructor : public Foo {
public:
explicit ListDestructor(ObserverListType* list) : list_(list) {}
~ListDestructor() override = default;
void Observe(int x) override { delete list_; }
private:
ObserverListType* list_;
};
TYPED_TEST(ObserverListTest, IteratorOutlivesList) {
DECLARE_TYPES;
ObserverListFoo* observer_list = new ObserverListFoo;
ListDestructor<ObserverListFoo> a(observer_list);
observer_list->AddObserver(&a);
for (auto& observer : *observer_list)
observer.Observe(0);
// There are no EXPECT* statements for this test, if we catch
// use-after-free errors for observer_list (eg with ASan) then
// this test has failed. See http://crbug.com/85296.
}
TYPED_TEST(ObserverListTest, BasicStdIterator) {
DECLARE_TYPES;
ObserverListFoo observer_list;
// An optimization: begin() and end() do not involve weak pointers on
// empty list.
EXPECT_FALSE(this->list(observer_list.begin()));
EXPECT_FALSE(this->list(observer_list.end()));
// Iterate over empty list: no effect, no crash.
for (auto& i : observer_list)
i.Observe(10);
Adder a(1), b(-1), c(1), d(-1);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (iterator i = observer_list.begin(), e = observer_list.end(); i != e; ++i)
i->Observe(1);
EXPECT_EQ(1, a.total);
EXPECT_EQ(-1, b.total);
EXPECT_EQ(1, c.total);
EXPECT_EQ(-1, d.total);
// Check an iteration over a 'const view' for a given container.
const ObserverListFoo& const_list = observer_list;
for (const_iterator i = const_list.begin(), e = const_list.end(); i != e;
++i) {
EXPECT_EQ(1, std::abs(i->GetValue()));
}
for (const auto& o : const_list)
EXPECT_EQ(1, std::abs(o.GetValue()));
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveItself) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& o : observer_list)
o.Observe(1);
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(11, a.total);
EXPECT_EQ(-11, b.total);
EXPECT_EQ(11, c.total);
EXPECT_EQ(-11, d.total);
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveBefore) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &b);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& o : observer_list)
o.Observe(1);
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(11, a.total);
EXPECT_EQ(-1, b.total);
EXPECT_EQ(11, c.total);
EXPECT_EQ(-11, d.total);
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveAfter) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &c);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& o : observer_list)
o.Observe(1);
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(11, a.total);
EXPECT_EQ(-11, b.total);
EXPECT_EQ(0, c.total);
EXPECT_EQ(-11, d.total);
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveAfterFront) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &a);
observer_list.AddObserver(&a);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&b);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& o : observer_list)
o.Observe(1);
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(1, a.total);
EXPECT_EQ(-11, b.total);
EXPECT_EQ(11, c.total);
EXPECT_EQ(-11, d.total);
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveBeforeBack) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &d);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&c);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&d);
for (auto& o : observer_list)
o.Observe(1);
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(11, a.total);
EXPECT_EQ(-11, b.total);
EXPECT_EQ(11, c.total);
EXPECT_EQ(0, d.total);
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveFront) {
DECLARE_TYPES;
using iterator = typename TestFixture::iterator;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
bool test_disruptor = true;
for (iterator i = observer_list.begin(), e = observer_list.end(); i != e;
++i) {
i->Observe(1);
// Check that second call to i->Observe() would crash here.
if (test_disruptor) {
EXPECT_FALSE(this->GetCurrent(&i));
test_disruptor = false;
}
}
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(11, a.total);
EXPECT_EQ(-11, b.total);
EXPECT_EQ(11, c.total);
EXPECT_EQ(-11, d.total);
}
TYPED_TEST(ObserverListTest, StdIteratorRemoveBack) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
observer_list.AddObserver(&disrupter);
for (auto& o : observer_list)
o.Observe(1);
for (auto& o : observer_list)
o.Observe(10);
EXPECT_EQ(11, a.total);
EXPECT_EQ(-11, b.total);
EXPECT_EQ(11, c.total);
EXPECT_EQ(-11, d.total);
}
TYPED_TEST(ObserverListTest, NestedLoop) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
observer_list.AddObserver(&disrupter);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (auto& o : observer_list) {
o.Observe(10);
for (auto& o : observer_list)
o.Observe(1);
}
EXPECT_EQ(15, a.total);
EXPECT_EQ(-15, b.total);
EXPECT_EQ(15, c.total);
EXPECT_EQ(-15, d.total);
}
TYPED_TEST(ObserverListTest, NonCompactList) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1);
Disrupter disrupter1(&observer_list, true);
Disrupter disrupter2(&observer_list, true);
// Disrupt itself and another one.
disrupter1.SetDoomed(&disrupter2);
observer_list.AddObserver(&disrupter1);
observer_list.AddObserver(&disrupter2);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
for (auto& o : observer_list) {
// Get the { nullptr, nullptr, &a, &b } non-compact list
// on the first inner pass.
o.Observe(10);
for (auto& o : observer_list)
o.Observe(1);
}
EXPECT_EQ(13, a.total);
EXPECT_EQ(-13, b.total);
}
TYPED_TEST(ObserverListTest, BecomesEmptyThanNonEmpty) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1);
Disrupter disrupter1(&observer_list, true);
Disrupter disrupter2(&observer_list, true);
// Disrupt itself and another one.
disrupter1.SetDoomed(&disrupter2);
observer_list.AddObserver(&disrupter1);
observer_list.AddObserver(&disrupter2);
bool add_observers = true;
for (auto& o : observer_list) {
// Get the { nullptr, nullptr } empty list on the first inner pass.
o.Observe(10);
for (auto& o : observer_list)
o.Observe(1);
if (add_observers) {
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
add_observers = false;
}
}
EXPECT_EQ(12, a.total);
EXPECT_EQ(-12, b.total);
}
TYPED_TEST(ObserverListTest, AddObserverInTheLastObserve) {
DECLARE_TYPES;
ObserverListFoo observer_list;
AddInObserve<ObserverListFoo> a(&observer_list);
Adder b(-1);
a.SetToAdd(&b);
observer_list.AddObserver(&a);
auto it = observer_list.begin();
while (it != observer_list.end()) {
auto& observer = *it;
// Intentionally increment the iterator before calling Observe(). The
// ObserverList starts with only one observer, and it == observer_list.end()
// should be true after the next line.
++it;
// However, the first Observe() call will add a second observer: at this
// point, it != observer_list.end() should be true, and Observe() should be
// called on the newly added observer on the next iteration of the loop.
observer.Observe(10);
}
EXPECT_EQ(-10, b.total);
}
class MockLogAssertHandler {
public:
MOCK_METHOD4(
HandleLogAssert,
void(const char*, int, const base::StringPiece, const base::StringPiece));
};
#if DCHECK_IS_ON()
TYPED_TEST(ObserverListTest, NonReentrantObserverList) {
DECLARE_TYPES;
using NonReentrantObserverListFoo = typename PickObserverList<
Foo>::template ObserverListType<Foo, /*check_empty=*/false,
/*allow_reentrancy=*/false>;
NonReentrantObserverListFoo non_reentrant_observer_list;
Adder a(1);
non_reentrant_observer_list.AddObserver(&a);
EXPECT_DCHECK_DEATH({
for (const Foo& a : non_reentrant_observer_list) {
for (const Foo& b : non_reentrant_observer_list) {
std::ignore = a;
std::ignore = b;
}
}
});
}
TYPED_TEST(ObserverListTest, ReentrantObserverList) {
DECLARE_TYPES;
using ReentrantObserverListFoo = typename PickObserverList<
Foo>::template ObserverListType<Foo, /*check_empty=*/false,
/*allow_reentrancy=*/true>;
ReentrantObserverListFoo reentrant_observer_list;
Adder a(1);
reentrant_observer_list.AddObserver(&a);
bool passed = false;
for (const Foo& a : reentrant_observer_list) {
for (const Foo& b : reentrant_observer_list) {
std::ignore = a;
std::ignore = b;
passed = true;
}
}
EXPECT_TRUE(passed);
}
#endif
class TestCheckedObserver : public CheckedObserver {
public:
explicit TestCheckedObserver(int* count) : count_(count) {}
void Observe() { ++(*count_); }
private:
int* count_;
DISALLOW_COPY_AND_ASSIGN(TestCheckedObserver);
};
// A second, identical observer, used to test multiple inheritance.
class TestCheckedObserver2 : public CheckedObserver {
public:
explicit TestCheckedObserver2(int* count) : count_(count) {}
void Observe() { ++(*count_); }
private:
int* count_;
DISALLOW_COPY_AND_ASSIGN(TestCheckedObserver2);
};
using CheckedObserverListTest = ::testing::Test;
// Test Observers that CHECK() when a UAF might otherwise occur.
TEST_F(CheckedObserverListTest, CheckedObserver) {
// See comments below about why this is unique_ptr.
auto list = std::make_unique<ObserverList<TestCheckedObserver>>();
int count1 = 0;
int count2 = 0;
TestCheckedObserver l1(&count1);
list->AddObserver(&l1);
{
TestCheckedObserver l2(&count2);
list->AddObserver(&l2);
for (auto& observer : *list)
observer.Observe();
EXPECT_EQ(1, count1);
EXPECT_EQ(1, count2);
}
{
auto it = list->begin();
it->Observe();
// For CheckedObservers, a CHECK() occurs when advancing the iterator. (On
// calling the observer method would be too late since the pointer would
// already be null by then).
EXPECT_CHECK_DEATH(it++);
// On the non-death fork, no UAF occurs since the deleted observer is never
// notified, but also the observer list still has |l2| in it. Check that.
list->RemoveObserver(&l1);
EXPECT_TRUE(list->might_have_observers());
// Now (in the non-death fork()) there's a problem. To delete |it|, we need
// to compact the list, but that needs to iterate, which would CHECK again.
// We can't remove |l2| (it's null). But we can delete |list|, which makes
// the weak pointer in the iterator itself null.
list.reset();
}
EXPECT_EQ(2, count1);
EXPECT_EQ(1, count2);
}
class MultiObserver : public TestCheckedObserver,
public TestCheckedObserver2,
public AdderT<UncheckedBase> {
public:
MultiObserver(int* checked_count, int* two_count)
: TestCheckedObserver(checked_count),
TestCheckedObserver2(two_count),
AdderT(1) {}
};
// Test that observers behave as expected when observing multiple interfaces
// with different traits.
TEST_F(CheckedObserverListTest, MultiObserver) {
// Observe two checked observer lists. This is to ensure the WeakPtrFactory
// in CheckedObserver can correctly service multiple ObserverLists.
ObserverList<TestCheckedObserver> checked_list;
ObserverList<TestCheckedObserver2> two_list;
ObserverList<UncheckedBase>::Unchecked unsafe_list;
int counts[2] = {};
auto observer = std::make_unique<MultiObserver>(&counts[0], &counts[1]);
two_list.AddObserver(observer.get());
checked_list.AddObserver(observer.get());
unsafe_list.AddObserver(observer.get());
auto iterate_over = [](auto* list) {
for (auto& observer : *list)
observer.Observe();
};
iterate_over(&two_list);
iterate_over(&checked_list);
for (auto& observer : unsafe_list)
observer.Observe(10);
EXPECT_EQ(10, observer->GetValue());
for (const auto& count : counts)
EXPECT_EQ(1, count);
unsafe_list.RemoveObserver(observer.get()); // Avoid a use-after-free.
observer.reset();
EXPECT_CHECK_DEATH(iterate_over(&checked_list));
for (const auto& count : counts)
EXPECT_EQ(1, count);
}
} // namespace base