// 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 #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 struct PickObserverList {}; template <> struct PickObserverList { template using ObserverListType = ObserverList; }; template <> struct PickObserverList { template using ObserverListType = typename ObserverList::Unchecked; }; template 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 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 list_; raw_ptr doomed_; bool remove_self_; }; template 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 observer_list; raw_ptr to_add_; }; template class ObserverListCreator : public DelegateSimpleThread::Delegate { public: std::unique_ptr Create( absl::optional 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(*policy_); } else { observer_list_ = std::make_unique(); } } std::unique_ptr observer_list_; absl::optional policy_; }; } // namespace class ObserverListTestBase { public: ObserverListTestBase() = default; ObserverListTestBase(const ObserverListTestBase&) = delete; ObserverListTestBase& operator=(const ObserverListTestBase&) = delete; template const decltype(T::list_.get()) list(const T& iter) { return iter.list_.get(); } template 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::iterator* iter) { EXPECT_DCHECK_DEATH(return iter->GetCurrent()); return nullptr; } }; // Templatized test fixture that can pick between CheckedBase and UncheckedBase. template class ObserverListTest : public ObserverListTestBase, public ::testing::Test { public: template using ObserverList = typename PickObserverList::template ObserverListType; using iterator = typename ObserverList::iterator; using const_iterator = typename ObserverList::const_iterator; ObserverListTest() = default; ObserverListTest(const ObserverListTest&) = delete; ObserverListTest& operator=(const ObserverListTest&) = delete; }; using ObserverTypes = ::testing::Types; 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::template ObserverListType; \ using Adder = AdderT; \ using Disrupter = DisrupterT; \ using const_iterator = typename TestFixture::const_iterator; \ using iterator = typename TestFixture::iterator; \ (void)reinterpret_cast(0); \ (void)reinterpret_cast(0); \ (void)reinterpret_cast(0); \ (void)reinterpret_cast(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 list_creator; Adder a(1); // Check with default constructor { std::unique_ptr 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 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; 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 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 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& adder() const { return adder_; } private: const raw_ptr list_; bool added_; AdderT adder_; }; TYPED_TEST(ObserverListTest, ClearNotifyAll) { DECLARE_TYPES; ObserverListFoo observer_list; AddInClearObserve 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 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 ListDestructor : public Foo { public: explicit ListDestructor(ObserverListType* list) : list_(list) {} ~ListDestructor() override = default; void Observe(int x) override { delete list_; } private: raw_ptr list_; }; TYPED_TEST(ObserverListTest, IteratorOutlivesList) { DECLARE_TYPES; ObserverListFoo* observer_list = new ObserverListFoo; ListDestructor 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 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& 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 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& 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 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; 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; 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 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 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>(); 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 { 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 checked_list; ObserverList two_list; ObserverList::Unchecked unsafe_list; int counts[2] = {}; auto multi_observer = std::make_unique(&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