1068 lines
29 KiB
C++
1068 lines
29 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// 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 <memory>
|
|
|
|
#include "base/memory/raw_ptr.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"
|
|
#include "third_party/abseil-cpp/absl/types/optional.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_.get());
|
|
}
|
|
|
|
void SetDoomed(Foo* doomed) { doomed_ = doomed; }
|
|
|
|
private:
|
|
raw_ptr<ObserverListType> list_;
|
|
raw_ptr<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_.get());
|
|
to_add_ = nullptr;
|
|
}
|
|
}
|
|
|
|
raw_ptr<ObserverListType> observer_list;
|
|
raw_ptr<Foo> to_add_;
|
|
};
|
|
|
|
template <class ObserverListType>
|
|
class ObserverListCreator : public DelegateSimpleThread::Delegate {
|
|
public:
|
|
std::unique_ptr<ObserverListType> Create(
|
|
absl::optional<base::ObserverListPolicy> policy = absl::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_;
|
|
absl::optional<base::ObserverListPolicy> policy_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class ObserverListTestBase {
|
|
public:
|
|
ObserverListTestBase() = default;
|
|
ObserverListTestBase(const ObserverListTestBase&) = delete;
|
|
ObserverListTestBase& operator=(const ObserverListTestBase&) = delete;
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
// 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() = default;
|
|
ObserverListTest(const ObserverListTest&) = delete;
|
|
ObserverListTest& operator=(const ObserverListTest&) = delete;
|
|
};
|
|
|
|
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)reinterpret_cast<Disrupter*>(0); \
|
|
(void)reinterpret_cast<Adder*>(0); \
|
|
(void)reinterpret_cast<const_iterator*>(0); \
|
|
(void)reinterpret_cast<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.empty());
|
|
|
|
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.empty());
|
|
EXPECT_EQ(&*ita, &a);
|
|
EXPECT_EQ(&*itb, &b);
|
|
|
|
ol.RemoveObserver(&a);
|
|
EXPECT_TRUE(!col.empty());
|
|
EXPECT_FALSE(col.HasObserver(&a));
|
|
EXPECT_EQ(&*itb, &b);
|
|
|
|
ol.RemoveObserver(&b);
|
|
EXPECT_FALSE(!col.empty());
|
|
EXPECT_FALSE(col.HasObserver(&a));
|
|
EXPECT_FALSE(col.HasObserver(&b));
|
|
|
|
it = It();
|
|
ita = It();
|
|
EXPECT_FALSE(!col.empty());
|
|
ita = itb;
|
|
itb = It();
|
|
EXPECT_FALSE(!col.empty());
|
|
ita = It();
|
|
EXPECT_FALSE(!col.empty());
|
|
}
|
|
|
|
ol.AddObserver(&a);
|
|
ol.AddObserver(&b);
|
|
EXPECT_TRUE(!col.empty());
|
|
ol.Clear();
|
|
EXPECT_FALSE(!col.empty());
|
|
|
|
ol.AddObserver(&a);
|
|
ol.AddObserver(&b);
|
|
EXPECT_TRUE(!col.empty());
|
|
{
|
|
const It it = col.begin();
|
|
ol.Clear();
|
|
EXPECT_FALSE(!col.empty());
|
|
}
|
|
EXPECT_FALSE(!col.empty());
|
|
}
|
|
|
|
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:
|
|
const raw_ptr<ObserverListType> 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_.ExtractAsDangling(); }
|
|
|
|
private:
|
|
raw_ptr<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& observer : observer_list) {
|
|
observer.Observe(10);
|
|
|
|
for (auto& nested_observer : observer_list)
|
|
nested_observer.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 disrupter2(&observer_list, true); // Must outlive `disrupter1`.
|
|
Disrupter disrupter1(&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& observer : observer_list) {
|
|
// Get the { nullptr, nullptr, &a, &b } non-compact list
|
|
// on the first inner pass.
|
|
observer.Observe(10);
|
|
|
|
for (auto& nested_observer : observer_list)
|
|
nested_observer.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 disrupter2(&observer_list, true); // Must outlive `disrupter1`.
|
|
Disrupter disrupter1(&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& observer : observer_list) {
|
|
// Get the { nullptr, nullptr } empty list on the first inner pass.
|
|
observer.Observe(10);
|
|
|
|
for (auto& nested_observer : observer_list)
|
|
nested_observer.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& observer : non_reentrant_observer_list) {
|
|
for (const Foo& nested_observer : non_reentrant_observer_list) {
|
|
std::ignore = observer;
|
|
std::ignore = nested_observer;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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& observer : reentrant_observer_list) {
|
|
for (const Foo& nested_observer : reentrant_observer_list) {
|
|
std::ignore = observer;
|
|
std::ignore = nested_observer;
|
|
passed = true;
|
|
}
|
|
}
|
|
EXPECT_TRUE(passed);
|
|
}
|
|
#endif
|
|
|
|
class TestCheckedObserver : public CheckedObserver {
|
|
public:
|
|
explicit TestCheckedObserver(int* count) : count_(count) {}
|
|
TestCheckedObserver(const TestCheckedObserver&) = delete;
|
|
TestCheckedObserver& operator=(const TestCheckedObserver&) = delete;
|
|
|
|
void Observe() { ++(*count_); }
|
|
|
|
private:
|
|
raw_ptr<int> count_;
|
|
};
|
|
|
|
// A second, identical observer, used to test multiple inheritance.
|
|
class TestCheckedObserver2 : public CheckedObserver {
|
|
public:
|
|
explicit TestCheckedObserver2(int* count) : count_(count) {}
|
|
TestCheckedObserver2(const TestCheckedObserver2&) = delete;
|
|
TestCheckedObserver2& operator=(const TestCheckedObserver2&) = delete;
|
|
|
|
void Observe() { ++(*count_); }
|
|
|
|
private:
|
|
raw_ptr<int> count_;
|
|
};
|
|
|
|
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->empty());
|
|
|
|
// 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 multi_observer = std::make_unique<MultiObserver>(&counts[0], &counts[1]);
|
|
two_list.AddObserver(multi_observer.get());
|
|
checked_list.AddObserver(multi_observer.get());
|
|
unsafe_list.AddObserver(multi_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, multi_observer->GetValue());
|
|
for (const auto& count : counts)
|
|
EXPECT_EQ(1, count);
|
|
|
|
unsafe_list.RemoveObserver(multi_observer.get()); // Avoid a use-after-free.
|
|
|
|
multi_observer.reset();
|
|
EXPECT_CHECK_DEATH(iterate_over(&checked_list));
|
|
|
|
for (const auto& count : counts)
|
|
EXPECT_EQ(1, count);
|
|
}
|
|
|
|
} // namespace base
|