Commit 30f97fdd authored by Trent Apted's avatar Trent Apted Committed by Commit Bot

base::CheckedObserver - a common base class for observers.

Turns a possible UAF when trying to notify a deleted observer into a
CHECK(). This is achieved with minimal changes to ObserverList and an
adapter that gives WeakPtr-like semantics to an observer interface.

base::ObserverList<T>::Unchecked continues to use raw pointers that
are unchecked.

Bug: 842987, 808318
Change-Id: I8ab20845f4f6e1d2559490287731cea2dbf40d39
Reviewed-on: https://chromium-review.googlesource.com/1053338
Commit-Queue: Trent Apted <tapted@chromium.org>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584694}
parent 952beb47
...@@ -294,6 +294,10 @@ jumbo_component("base") { ...@@ -294,6 +294,10 @@ jumbo_component("base") {
"cpu.h", "cpu.h",
"critical_closure.h", "critical_closure.h",
"critical_closure_internal_ios.mm", "critical_closure_internal_ios.mm",
"observer_list_internal.cc",
"observer_list_internal.h",
"observer_list_types.cc",
"observer_list_types.h",
# This file depends on files from the "debug/allocator" target, # This file depends on files from the "debug/allocator" target,
# but this target does not depend on "debug/allocator". # but this target does not depend on "debug/allocator".
...@@ -2792,6 +2796,7 @@ if (enable_nocompile_tests) { ...@@ -2792,6 +2796,7 @@ if (enable_nocompile_tests) {
"memory/weak_ptr_unittest.nc", "memory/weak_ptr_unittest.nc",
"metrics/field_trial_params_unittest.nc", "metrics/field_trial_params_unittest.nc",
"metrics/histogram_unittest.nc", "metrics/histogram_unittest.nc",
"observer_list_unittest.nc",
"optional_unittest.nc", "optional_unittest.nc",
"strings/string16_unittest.nc", "strings/string16_unittest.nc",
"task/task_traits_extension_unittest.nc", "task/task_traits_extension_unittest.nc",
......
...@@ -268,6 +268,11 @@ class WeakPtr : public internal::WeakPtrBase { ...@@ -268,6 +268,11 @@ class WeakPtr : public internal::WeakPtrBase {
// instance isn't being re-assigned or reset() racily with this call. // instance isn't being re-assigned or reset() racily with this call.
bool MaybeValid() const { return ref_.MaybeValid(); } bool MaybeValid() const { return ref_.MaybeValid(); }
// Returns whether the object |this| points to has been invalidated. This can
// be used to distinguish a WeakPtr to a destroyed object from one that has
// been explicitly set to null. A null WeakPtr is always valid.
bool WasInvalidated() const { return ptr_ && !ref_.IsValid(); }
private: private:
friend class internal::SupportsWeakPtrBase; friend class internal::SupportsWeakPtrBase;
template <typename U> friend class WeakPtr; template <typename U> friend class WeakPtr;
......
...@@ -392,6 +392,49 @@ TEST(WeakPtrTest, InvalidateWeakPtrs) { ...@@ -392,6 +392,49 @@ TEST(WeakPtrTest, InvalidateWeakPtrs) {
EXPECT_FALSE(factory.HasWeakPtrs()); EXPECT_FALSE(factory.HasWeakPtrs());
} }
// Tests that WasInvalidated() is true only for invalidated WeakPtrs (not
// nullptr) and doesn't DCHECK.
TEST(WeakPtrTest, WasInvalidatedByFactoryDestruction) {
WeakPtr<int> ptr;
EXPECT_FALSE(ptr.WasInvalidated());
// Test |data| destroyed.
{
int data;
WeakPtrFactory<int> factory(&data);
ptr = factory.GetWeakPtr();
EXPECT_FALSE(ptr.WasInvalidated());
}
EXPECT_TRUE(ptr.WasInvalidated()); // Shouldn't tickle asan.
ptr = nullptr;
EXPECT_FALSE(ptr.WasInvalidated());
}
// As above, but testing InvalidateWeakPtrs().
TEST(WeakPtrTest, WasInvalidatedByInvalidateWeakPtrs) {
int data;
WeakPtrFactory<int> factory(&data);
WeakPtr<int> ptr = factory.GetWeakPtr();
EXPECT_FALSE(ptr.WasInvalidated());
factory.InvalidateWeakPtrs();
EXPECT_TRUE(ptr.WasInvalidated());
ptr = nullptr;
EXPECT_FALSE(ptr.WasInvalidated());
}
// Test WasInvalidated() when assigning null before invalidating.
TEST(WeakPtrTest, WasInvalidatedWhilstNull) {
int data;
WeakPtrFactory<int> factory(&data);
WeakPtr<int> ptr = factory.GetWeakPtr();
EXPECT_FALSE(ptr.WasInvalidated());
ptr = nullptr;
EXPECT_FALSE(ptr.WasInvalidated());
factory.InvalidateWeakPtrs();
EXPECT_FALSE(ptr.WasInvalidated());
}
TEST(WeakPtrTest, MaybeValidOnSameSequence) { TEST(WeakPtrTest, MaybeValidOnSameSequence) {
int data; int data;
WeakPtrFactory<int> factory(&data); WeakPtrFactory<int> factory(&data);
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/observer_list_internal.h"
#include "base/stl_util.h" #include "base/stl_util.h"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
...@@ -94,16 +95,24 @@ enum class ObserverListPolicy { ...@@ -94,16 +95,24 @@ enum class ObserverListPolicy {
// TODO(oshima): Change the default to non reentrant. https://crbug.com/812109 // TODO(oshima): Change the default to non reentrant. https://crbug.com/812109
template <class ObserverType, template <class ObserverType,
bool check_empty = false, bool check_empty = false,
bool allow_reentrancy = true> bool allow_reentrancy = true,
class ObserverList class ObserverStorageType = internal::CheckedObserverAdapter>
: public SupportsWeakPtr< class ObserverList : public SupportsWeakPtr<ObserverList<ObserverType,
ObserverList<ObserverType, check_empty, allow_reentrancy>> { check_empty,
allow_reentrancy,
ObserverStorageType>> {
public: public:
// Temporary type alias for introducing base::CheckedObserver. Existing // Allow declaring an ObserverList<...>::Unchecked that replaces the default
// ObserverLists will be Unchecked during the migration. // ObserverStorageType to use raw pointers. This is required to support legacy
// TODO(https://crbug.com/842987): Use the Unchecked storage type when that // observers that do not inherit from CheckedObserver. The majority of NEW
// template param is added. // CODE SHOULD NOT USE THIS, but it may be suited for performance-critical
using Unchecked = ObserverList<ObserverType, check_empty, allow_reentrancy>; // situations to avoid overheads of a CHECK(). Note the type can't be chosen
// based on ObserverType's definition because ObserverLists are often declared
// in headers using a forward-declare of ObserverType.
using Unchecked = ObserverList<ObserverType,
check_empty,
allow_reentrancy,
internal::UncheckedObserverAdapter>;
// An iterator class that can be used to access the list of observers. // An iterator class that can be used to access the list of observers.
class Iter { class Iter {
...@@ -187,21 +196,23 @@ class ObserverList ...@@ -187,21 +196,23 @@ class ObserverList
} }
private: private:
FRIEND_TEST_ALL_PREFIXES(ObserverListTest, BasicStdIterator); friend class ObserverListTestBase;
FRIEND_TEST_ALL_PREFIXES(ObserverListTest, StdIteratorRemoveFront);
ObserverType* GetCurrent() const { ObserverType* GetCurrent() const {
DCHECK(list_); DCHECK(list_);
DCHECK_LT(index_, clamped_max_index()); DCHECK_LT(index_, clamped_max_index());
return list_->observers_[index_]; return ObserverStorageType::template Get<ObserverType>(
list_->observers_[index_]);
} }
void EnsureValidIndex() { void EnsureValidIndex() {
DCHECK(list_); DCHECK(list_);
const size_t max_index = clamped_max_index(); const size_t max_index = clamped_max_index();
while (index_ < max_index && !list_->observers_[index_]) while (index_ < max_index &&
list_->observers_[index_].IsMarkedForRemoval()) {
++index_; ++index_;
} }
}
size_t clamped_max_index() const { size_t clamped_max_index() const {
return std::min(max_index_, list_->observers_.size()); return std::min(max_index_, list_->observers_.size());
...@@ -220,6 +231,7 @@ class ObserverList ...@@ -220,6 +231,7 @@ class ObserverList
using iterator = Iter; using iterator = Iter;
using const_iterator = Iter; using const_iterator = Iter;
using value_type = ObserverType;
const_iterator begin() const { const_iterator begin() const {
// An optimization: do not involve weak pointers for empty list. // An optimization: do not involve weak pointers for empty list.
...@@ -249,20 +261,22 @@ class ObserverList ...@@ -249,20 +261,22 @@ class ObserverList
NOTREACHED() << "Observers can only be added once!"; NOTREACHED() << "Observers can only be added once!";
return; return;
} }
observers_.push_back(obs); observers_.emplace_back(ObserverStorageType(obs));
} }
// Removes the given observer from this list. Does nothing if this observer is // Removes the given observer from this list. Does nothing if this observer is
// not in this list. // not in this list.
void RemoveObserver(const ObserverType* obs) { void RemoveObserver(const ObserverType* obs) {
DCHECK(obs); DCHECK(obs);
const auto it = std::find(observers_.begin(), observers_.end(), obs); const auto it =
std::find_if(observers_.begin(), observers_.end(),
[obs](const auto& o) { return o.IsEqual(obs); });
if (it == observers_.end()) if (it == observers_.end())
return; return;
DCHECK_GE(live_iterator_count_, 0); DCHECK_GE(live_iterator_count_, 0);
if (live_iterator_count_) { if (live_iterator_count_) {
*it = nullptr; it->MarkForRemoval();
} else { } else {
observers_.erase(it); observers_.erase(it);
} }
...@@ -270,14 +284,22 @@ class ObserverList ...@@ -270,14 +284,22 @@ class ObserverList
// Determine whether a particular observer is in the list. // Determine whether a particular observer is in the list.
bool HasObserver(const ObserverType* obs) const { bool HasObserver(const ObserverType* obs) const {
return ContainsValue(observers_, obs); // Client code passing null could be confused by the treatment of observers
// removed mid-iteration. TODO(tapted): This should probably DCHECK, but
// some client code currently does pass null to HasObserver().
if (obs == nullptr)
return false;
return std::find_if(observers_.begin(), observers_.end(),
[obs](const auto& o) { return o.IsEqual(obs); }) !=
observers_.end();
} }
// Removes all the observers from this list. // Removes all the observers from this list.
void Clear() { void Clear() {
DCHECK_GE(live_iterator_count_, 0); DCHECK_GE(live_iterator_count_, 0);
if (live_iterator_count_) { if (live_iterator_count_) {
std::fill(observers_.begin(), observers_.end(), nullptr); for (auto& observer : observers_)
observer.MarkForRemoval();
} else { } else {
observers_.clear(); observers_.clear();
} }
...@@ -286,13 +308,12 @@ class ObserverList ...@@ -286,13 +308,12 @@ class ObserverList
bool might_have_observers() const { return !observers_.empty(); } bool might_have_observers() const { return !observers_.empty(); }
private: private:
// Compacts list of observers by removing null pointers. // Compacts list of observers by removing those marked for removal.
void Compact() { void Compact() {
observers_.erase(std::remove(observers_.begin(), observers_.end(), nullptr), EraseIf(observers_, [](const auto& o) { return o.IsMarkedForRemoval(); });
observers_.end());
} }
std::vector<ObserverType*> observers_; std::vector<ObserverStorageType> observers_;
// Number of active iterators referencing this ObserverList. // Number of active iterators referencing this ObserverList.
// //
......
// Copyright 2018 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_internal.h"
namespace base {
namespace internal {
CheckedObserverAdapter::CheckedObserverAdapter(const CheckedObserver* observer)
: weak_ptr_(observer->factory_.GetWeakPtr()) {}
CheckedObserverAdapter::CheckedObserverAdapter(CheckedObserverAdapter&& other) =
default;
CheckedObserverAdapter& CheckedObserverAdapter::operator=(
CheckedObserverAdapter&& other) = default;
CheckedObserverAdapter::~CheckedObserverAdapter() = default;
} // namespace internal
} // namespace base
// Copyright 2018 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.
#ifndef BASE_OBSERVER_LIST_INTERNAL_H_
#define BASE_OBSERVER_LIST_INTERNAL_H_
#include "base/base_export.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list_types.h"
namespace base {
namespace internal {
// Adapter for putting raw pointers into an ObserverList<Foo>::Unchecked.
class BASE_EXPORT UncheckedObserverAdapter {
public:
explicit UncheckedObserverAdapter(const void* observer)
: ptr_(const_cast<void*>(observer)) {}
UncheckedObserverAdapter(UncheckedObserverAdapter&& other) = default;
UncheckedObserverAdapter& operator=(UncheckedObserverAdapter&& other) =
default;
void MarkForRemoval() { ptr_ = nullptr; }
bool IsMarkedForRemoval() const { return !ptr_; }
bool IsEqual(const void* rhs) const { return ptr_ == rhs; }
template <class ObserverType>
static ObserverType* Get(const UncheckedObserverAdapter& adapter) {
static_assert(
!std::is_base_of<CheckedObserver, ObserverType>::value,
"CheckedObserver classes must not use ObserverList<T>::Unchecked.");
return static_cast<ObserverType*>(adapter.ptr_);
}
private:
void* ptr_;
// Although copying works, disallow it to be consistent with
// CheckedObserverAdapter.
DISALLOW_COPY_AND_ASSIGN(UncheckedObserverAdapter);
};
// Adapter for CheckedObserver types so that they can use the same syntax as a
// raw pointer when stored in the std::vector of observers in an ObserverList.
// It wraps a WeakPtr<CheckedObserver> and allows a "null" pointer via
// destruction to be distinguished from an observer marked for deferred removal
// whilst an iteration is in progress.
class BASE_EXPORT CheckedObserverAdapter {
public:
explicit CheckedObserverAdapter(const CheckedObserver* observer);
// Move-only construction and assignment is required to store this in STL
// types.
CheckedObserverAdapter(CheckedObserverAdapter&& other);
CheckedObserverAdapter& operator=(CheckedObserverAdapter&& other);
~CheckedObserverAdapter();
void MarkForRemoval() {
DCHECK(weak_ptr_);
weak_ptr_.reset();
}
bool IsMarkedForRemoval() const {
// If |weak_ptr_| was invalidated then this attempt to iterate over the
// pointer is a UAF. Tip: If it's unclear where the `delete` occurred, try
// adding CHECK(!IsInObserverList()) to the ~CheckedObserver() (destructor)
// override. However, note that this is not always a bug: a destroyed
// observer can exist in an ObserverList so long as nothing iterates over
// the ObserverList before the list itself is destroyed.
CHECK(!weak_ptr_.WasInvalidated());
return weak_ptr_ == nullptr;
}
bool IsEqual(const CheckedObserver* rhs) const {
// Note that inside an iteration, ObserverList::HasObserver() may call this
// and |weak_ptr_| may be null due to a deferred removal, which is fine.
return weak_ptr_.get() == rhs;
}
template <class ObserverType>
static ObserverType* Get(const CheckedObserverAdapter& adapter) {
static_assert(
std::is_base_of<CheckedObserver, ObserverType>::value,
"Observers should inherit from base::CheckedObserver. "
"Use ObserverList<T>::Unchecked to observe with raw pointers.");
DCHECK(adapter.weak_ptr_);
return static_cast<ObserverType*>(adapter.weak_ptr_.get());
}
private:
WeakPtr<CheckedObserver> weak_ptr_;
DISALLOW_COPY_AND_ASSIGN(CheckedObserverAdapter);
};
} // namespace internal
} // namespace base
#endif // BASE_OBSERVER_LIST_INTERNAL_H_
// Copyright 2018 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_types.h"
namespace base {
CheckedObserver::CheckedObserver() : factory_(this) {}
CheckedObserver::~CheckedObserver() = default;
bool CheckedObserver::IsInObserverList() const {
return factory_.HasWeakPtrs();
}
} // namespace base
// Copyright 2018 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.
#ifndef BASE_OBSERVER_LIST_TYPES_H_
#define BASE_OBSERVER_LIST_TYPES_H_
#include "base/base_export.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
namespace base {
namespace internal {
class CheckedObserverAdapter;
}
// A CheckedObserver serves as a base class for an observer interface designed
// to be used with base::ObserverList. It helps detect potential use-after-free
// issues that can occur when observers fail to remove themselves from an
// observer list upon destruction.
//
// A CheckedObserver will CHECK() if an ObserverList iteration is attempted over
// a destroyed Observer.
//
// Note that a CheckedObserver subclass must be deleted on the same thread as
// the ObserverList(s) it is added to. This is DCHECK()ed via WeakPtr.
class BASE_EXPORT CheckedObserver {
public:
CheckedObserver();
protected:
virtual ~CheckedObserver();
// Returns whether |this| is in any ObserverList. Subclasses can CHECK() this
// in their destructor to obtain a nicer stacktrace.
bool IsInObserverList() const;
private:
friend class internal::CheckedObserverAdapter;
// Must be mutable to allow ObserverList<const Foo>.
mutable WeakPtrFactory<CheckedObserver> factory_;
DISALLOW_COPY_AND_ASSIGN(CheckedObserver);
};
} // namespace base
#endif // BASE_OBSERVER_LIST_TYPES_H_
...@@ -13,17 +13,48 @@ ...@@ -13,17 +13,48 @@
namespace base { namespace base {
namespace { namespace {
class Foo { class CheckedBase : public CheckedObserver {
public: public:
virtual void Observe(int x) = 0; virtual void Observe(int x) = 0;
virtual ~Foo() = default; ~CheckedBase() override = default;
virtual int GetValue() const { return 0; } virtual int GetValue() const { return 0; }
}; };
class Adder : public Foo { class UncheckedBase {
public: public:
explicit Adder(int scaler) : total(0), scaler_(scaler) {} virtual void Observe(int x) = 0;
~Adder() override = default; virtual ~UncheckedBase() = default;
virtual int GetValue() const { return 0; }
};
// Helper for TYPED_TEST_CASE 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_; } void Observe(int x) override { total += x * scaler_; }
int GetValue() const override { return total; } int GetValue() const override { return total; }
...@@ -34,16 +65,18 @@ class Adder : public Foo { ...@@ -34,16 +65,18 @@ class Adder : public Foo {
int scaler_; int scaler_;
}; };
class Disrupter : public Foo { template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class DisrupterT : public Foo {
public: public:
Disrupter(ObserverList<Foo>* list, Foo* doomed, bool remove_self) DisrupterT(ObserverListType* list, Foo* doomed, bool remove_self)
: list_(list), doomed_(doomed), remove_self_(remove_self) {} : list_(list), doomed_(doomed), remove_self_(remove_self) {}
Disrupter(ObserverList<Foo>* list, Foo* doomed) DisrupterT(ObserverListType* list, Foo* doomed)
: Disrupter(list, doomed, false) {} : DisrupterT(list, doomed, false) {}
Disrupter(ObserverList<Foo>* list, bool remove_self) DisrupterT(ObserverListType* list, bool remove_self)
: Disrupter(list, nullptr, remove_self) {} : DisrupterT(list, nullptr, remove_self) {}
~Disrupter() override = default; ~DisrupterT() override = default;
void Observe(int x) override { void Observe(int x) override {
if (remove_self_) if (remove_self_)
...@@ -55,14 +88,16 @@ class Disrupter : public Foo { ...@@ -55,14 +88,16 @@ class Disrupter : public Foo {
void SetDoomed(Foo* doomed) { doomed_ = doomed; } void SetDoomed(Foo* doomed) { doomed_ = doomed; }
private: private:
ObserverList<Foo>* list_; ObserverListType* list_;
Foo* doomed_; Foo* doomed_;
bool remove_self_; bool remove_self_;
}; };
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class AddInObserve : public Foo { class AddInObserve : public Foo {
public: public:
explicit AddInObserve(ObserverList<Foo>* observer_list) explicit AddInObserve(ObserverListType* observer_list)
: observer_list(observer_list), to_add_() {} : observer_list(observer_list), to_add_() {}
void SetToAdd(Foo* to_add) { to_add_ = to_add; } void SetToAdd(Foo* to_add) { to_add_ = to_add; }
...@@ -74,24 +109,89 @@ class AddInObserve : public Foo { ...@@ -74,24 +109,89 @@ class AddInObserve : public Foo {
} }
} }
ObserverList<Foo>* observer_list; ObserverListType* observer_list;
Foo* to_add_; Foo* to_add_;
}; };
} // namespace } // namespace
TEST(ObserverListTest, BasicTest) { class ObserverListTestBase {
ObserverList<Foo> observer_list; public:
const ObserverList<Foo>& const_observer_list = observer_list; ObserverListTestBase() {}
template <class T>
const decltype(T::list_) list(const T& iter) {
return iter.list_;
}
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_CASE(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 ObserverList<Foo>::const_iterator it1 = const_observer_list.begin(); const const_iterator it1 = const_observer_list.begin();
EXPECT_EQ(it1, const_observer_list.end()); EXPECT_EQ(it1, const_observer_list.end());
// Iterator copy. // Iterator copy.
const ObserverList<Foo>::const_iterator it2 = it1; const const_iterator it2 = it1;
EXPECT_EQ(it2, it1); EXPECT_EQ(it2, it1);
// Iterator assignment. // Iterator assignment.
ObserverList<Foo>::const_iterator it3; const_iterator it3;
it3 = it2; it3 = it2;
EXPECT_EQ(it3, it1); EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2); EXPECT_EQ(it3, it2);
...@@ -102,13 +202,13 @@ TEST(ObserverListTest, BasicTest) { ...@@ -102,13 +202,13 @@ TEST(ObserverListTest, BasicTest) {
} }
{ {
const ObserverList<Foo>::iterator it1 = observer_list.begin(); const iterator it1 = observer_list.begin();
EXPECT_EQ(it1, observer_list.end()); EXPECT_EQ(it1, observer_list.end());
// Iterator copy. // Iterator copy.
const ObserverList<Foo>::iterator it2 = it1; const iterator it2 = it1;
EXPECT_EQ(it2, it1); EXPECT_EQ(it2, it1);
// Iterator assignment. // Iterator assignment.
ObserverList<Foo>::iterator it3; iterator it3;
it3 = it2; it3 = it2;
EXPECT_EQ(it3, it1); EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2); EXPECT_EQ(it3, it2);
...@@ -128,14 +228,14 @@ TEST(ObserverListTest, BasicTest) { ...@@ -128,14 +228,14 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_FALSE(const_observer_list.HasObserver(&c)); EXPECT_FALSE(const_observer_list.HasObserver(&c));
{ {
const ObserverList<Foo>::const_iterator it1 = const_observer_list.begin(); const const_iterator it1 = const_observer_list.begin();
EXPECT_NE(it1, const_observer_list.end()); EXPECT_NE(it1, const_observer_list.end());
// Iterator copy. // Iterator copy.
const ObserverList<Foo>::const_iterator it2 = it1; const const_iterator it2 = it1;
EXPECT_EQ(it2, it1); EXPECT_EQ(it2, it1);
EXPECT_NE(it2, const_observer_list.end()); EXPECT_NE(it2, const_observer_list.end());
// Iterator assignment. // Iterator assignment.
ObserverList<Foo>::const_iterator it3; const_iterator it3;
it3 = it2; it3 = it2;
EXPECT_EQ(it3, it1); EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2); EXPECT_EQ(it3, it2);
...@@ -144,21 +244,21 @@ TEST(ObserverListTest, BasicTest) { ...@@ -144,21 +244,21 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(it3, it1); EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2); EXPECT_EQ(it3, it2);
// Iterator post increment. // Iterator post increment.
ObserverList<Foo>::const_iterator it4 = it3++; const_iterator it4 = it3++;
EXPECT_EQ(it4, it1); EXPECT_EQ(it4, it1);
EXPECT_EQ(it4, it2); EXPECT_EQ(it4, it2);
EXPECT_NE(it4, it3); EXPECT_NE(it4, it3);
} }
{ {
const ObserverList<Foo>::iterator it1 = observer_list.begin(); const iterator it1 = observer_list.begin();
EXPECT_NE(it1, observer_list.end()); EXPECT_NE(it1, observer_list.end());
// Iterator copy. // Iterator copy.
const ObserverList<Foo>::iterator it2 = it1; const iterator it2 = it1;
EXPECT_EQ(it2, it1); EXPECT_EQ(it2, it1);
EXPECT_NE(it2, observer_list.end()); EXPECT_NE(it2, observer_list.end());
// Iterator assignment. // Iterator assignment.
ObserverList<Foo>::iterator it3; iterator it3;
it3 = it2; it3 = it2;
EXPECT_EQ(it3, it1); EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2); EXPECT_EQ(it3, it2);
...@@ -167,7 +267,7 @@ TEST(ObserverListTest, BasicTest) { ...@@ -167,7 +267,7 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(it3, it1); EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2); EXPECT_EQ(it3, it2);
// Iterator post increment. // Iterator post increment.
ObserverList<Foo>::iterator it4 = it3++; iterator it4 = it3++;
EXPECT_EQ(it4, it1); EXPECT_EQ(it4, it1);
EXPECT_EQ(it4, it2); EXPECT_EQ(it4, it2);
EXPECT_NE(it4, it3); EXPECT_NE(it4, it3);
...@@ -193,9 +293,13 @@ TEST(ObserverListTest, BasicTest) { ...@@ -193,9 +293,13 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(0, e.total); EXPECT_EQ(0, e.total);
} }
TEST(ObserverListTest, CompactsWhenNoActiveIterator) { TYPED_TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
ObserverList<const Foo> ol; DECLARE_TYPES;
const ObserverList<const Foo>& col = ol; using ObserverListConstFoo =
typename TestFixture::template ObserverList<const Foo>;
ObserverListConstFoo ol;
const ObserverListConstFoo& col = ol;
const Adder a(1); const Adder a(1);
const Adder b(2); const Adder b(2);
...@@ -209,7 +313,7 @@ TEST(ObserverListTest, CompactsWhenNoActiveIterator) { ...@@ -209,7 +313,7 @@ TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
EXPECT_TRUE(col.might_have_observers()); EXPECT_TRUE(col.might_have_observers());
using It = ObserverList<const Foo>::const_iterator; using It = typename ObserverListConstFoo::const_iterator;
{ {
It it = col.begin(); It it = col.begin();
...@@ -263,8 +367,9 @@ TEST(ObserverListTest, CompactsWhenNoActiveIterator) { ...@@ -263,8 +367,9 @@ TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
EXPECT_FALSE(col.might_have_observers()); EXPECT_FALSE(col.might_have_observers());
} }
TEST(ObserverListTest, DisruptSelf) { TYPED_TEST(ObserverListTest, DisruptSelf) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter evil(&observer_list, true); Disrupter evil(&observer_list, true);
...@@ -287,8 +392,9 @@ TEST(ObserverListTest, DisruptSelf) { ...@@ -287,8 +392,9 @@ TEST(ObserverListTest, DisruptSelf) {
EXPECT_EQ(-10, d.total); EXPECT_EQ(-10, d.total);
} }
TEST(ObserverListTest, DisruptBefore) { TYPED_TEST(ObserverListTest, DisruptBefore) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter evil(&observer_list, &b); Disrupter evil(&observer_list, &b);
...@@ -309,10 +415,11 @@ TEST(ObserverListTest, DisruptBefore) { ...@@ -309,10 +415,11 @@ TEST(ObserverListTest, DisruptBefore) {
EXPECT_EQ(-20, d.total); EXPECT_EQ(-20, d.total);
} }
TEST(ObserverListTest, Existing) { TYPED_TEST(ObserverListTest, Existing) {
ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY); DECLARE_TYPES;
ObserverListFoo observer_list(ObserverListPolicy::EXISTING_ONLY);
Adder a(1); Adder a(1);
AddInObserve b(&observer_list); AddInObserve<ObserverListFoo> b(&observer_list);
Adder c(1); Adder c(1);
b.SetToAdd(&c); b.SetToAdd(&c);
...@@ -333,9 +440,11 @@ TEST(ObserverListTest, Existing) { ...@@ -333,9 +440,11 @@ TEST(ObserverListTest, Existing) {
EXPECT_EQ(1, c.total); EXPECT_EQ(1, c.total);
} }
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class AddInClearObserve : public Foo { class AddInClearObserve : public Foo {
public: public:
explicit AddInClearObserve(ObserverList<Foo>* list) explicit AddInClearObserve(ObserverListType* list)
: list_(list), added_(false), adder_(1) {} : list_(list), added_(false), adder_(1) {}
void Observe(int /* x */) override { void Observe(int /* x */) override {
...@@ -345,18 +454,19 @@ class AddInClearObserve : public Foo { ...@@ -345,18 +454,19 @@ class AddInClearObserve : public Foo {
} }
bool added() const { return added_; } bool added() const { return added_; }
const Adder& adder() const { return adder_; } const AdderT<Foo>& adder() const { return adder_; }
private: private:
ObserverList<Foo>* const list_; ObserverListType* const list_;
bool added_; bool added_;
Adder adder_; AdderT<Foo> adder_;
}; };
TEST(ObserverListTest, ClearNotifyAll) { TYPED_TEST(ObserverListTest, ClearNotifyAll) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
AddInClearObserve a(&observer_list); ObserverListFoo observer_list;
AddInClearObserve<ObserverListFoo> a(&observer_list);
observer_list.AddObserver(&a); observer_list.AddObserver(&a);
...@@ -367,9 +477,10 @@ TEST(ObserverListTest, ClearNotifyAll) { ...@@ -367,9 +477,10 @@ TEST(ObserverListTest, ClearNotifyAll) {
<< "Adder should observe once and have sum of 1."; << "Adder should observe once and have sum of 1.";
} }
TEST(ObserverListTest, ClearNotifyExistingOnly) { TYPED_TEST(ObserverListTest, ClearNotifyExistingOnly) {
ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY); DECLARE_TYPES;
AddInClearObserve a(&observer_list); ObserverListFoo observer_list(ObserverListPolicy::EXISTING_ONLY);
AddInClearObserve<ObserverListFoo> a(&observer_list);
observer_list.AddObserver(&a); observer_list.AddObserver(&a);
...@@ -380,21 +491,23 @@ TEST(ObserverListTest, ClearNotifyExistingOnly) { ...@@ -380,21 +491,23 @@ TEST(ObserverListTest, ClearNotifyExistingOnly) {
<< "Adder should not observe, so sum should still be 0."; << "Adder should not observe, so sum should still be 0.";
} }
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class ListDestructor : public Foo { class ListDestructor : public Foo {
public: public:
explicit ListDestructor(ObserverList<Foo>* list) : list_(list) {} explicit ListDestructor(ObserverListType* list) : list_(list) {}
~ListDestructor() override = default; ~ListDestructor() override = default;
void Observe(int x) override { delete list_; } void Observe(int x) override { delete list_; }
private: private:
ObserverList<Foo>* list_; ObserverListType* list_;
}; };
TYPED_TEST(ObserverListTest, IteratorOutlivesList) {
TEST(ObserverListTest, IteratorOutlivesList) { DECLARE_TYPES;
ObserverList<Foo>* observer_list = new ObserverList<Foo>; ObserverListFoo* observer_list = new ObserverListFoo;
ListDestructor a(observer_list); ListDestructor<ObserverListFoo> a(observer_list);
observer_list->AddObserver(&a); observer_list->AddObserver(&a);
for (auto& observer : *observer_list) for (auto& observer : *observer_list)
...@@ -405,14 +518,14 @@ TEST(ObserverListTest, IteratorOutlivesList) { ...@@ -405,14 +518,14 @@ TEST(ObserverListTest, IteratorOutlivesList) {
// this test has failed. See http://crbug.com/85296. // this test has failed. See http://crbug.com/85296.
} }
TEST(ObserverListTest, BasicStdIterator) { TYPED_TEST(ObserverListTest, BasicStdIterator) {
using FooList = ObserverList<Foo>; DECLARE_TYPES;
FooList observer_list; ObserverListFoo observer_list;
// An optimization: begin() and end() do not involve weak pointers on // An optimization: begin() and end() do not involve weak pointers on
// empty list. // empty list.
EXPECT_FALSE(observer_list.begin().list_); EXPECT_FALSE(this->list(observer_list.begin()));
EXPECT_FALSE(observer_list.end().list_); EXPECT_FALSE(this->list(observer_list.end()));
// Iterate over empty list: no effect, no crash. // Iterate over empty list: no effect, no crash.
for (auto& i : observer_list) for (auto& i : observer_list)
...@@ -425,8 +538,7 @@ TEST(ObserverListTest, BasicStdIterator) { ...@@ -425,8 +538,7 @@ TEST(ObserverListTest, BasicStdIterator) {
observer_list.AddObserver(&c); observer_list.AddObserver(&c);
observer_list.AddObserver(&d); observer_list.AddObserver(&d);
for (FooList::iterator i = observer_list.begin(), e = observer_list.end(); for (iterator i = observer_list.begin(), e = observer_list.end(); i != e; ++i)
i != e; ++i)
i->Observe(1); i->Observe(1);
EXPECT_EQ(1, a.total); EXPECT_EQ(1, a.total);
...@@ -435,9 +547,9 @@ TEST(ObserverListTest, BasicStdIterator) { ...@@ -435,9 +547,9 @@ TEST(ObserverListTest, BasicStdIterator) {
EXPECT_EQ(-1, d.total); EXPECT_EQ(-1, d.total);
// Check an iteration over a 'const view' for a given container. // Check an iteration over a 'const view' for a given container.
const FooList& const_list = observer_list; const ObserverListFoo& const_list = observer_list;
for (FooList::const_iterator i = const_list.begin(), e = const_list.end(); for (const_iterator i = const_list.begin(), e = const_list.end(); i != e;
i != e; ++i) { ++i) {
EXPECT_EQ(1, std::abs(i->GetValue())); EXPECT_EQ(1, std::abs(i->GetValue()));
} }
...@@ -445,8 +557,9 @@ TEST(ObserverListTest, BasicStdIterator) { ...@@ -445,8 +557,9 @@ TEST(ObserverListTest, BasicStdIterator) {
EXPECT_EQ(1, std::abs(o.GetValue())); EXPECT_EQ(1, std::abs(o.GetValue()));
} }
TEST(ObserverListTest, StdIteratorRemoveItself) { TYPED_TEST(ObserverListTest, StdIteratorRemoveItself) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true); Disrupter disrupter(&observer_list, true);
...@@ -468,8 +581,9 @@ TEST(ObserverListTest, StdIteratorRemoveItself) { ...@@ -468,8 +581,9 @@ TEST(ObserverListTest, StdIteratorRemoveItself) {
EXPECT_EQ(-11, d.total); EXPECT_EQ(-11, d.total);
} }
TEST(ObserverListTest, StdIteratorRemoveBefore) { TYPED_TEST(ObserverListTest, StdIteratorRemoveBefore) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &b); Disrupter disrupter(&observer_list, &b);
...@@ -491,8 +605,9 @@ TEST(ObserverListTest, StdIteratorRemoveBefore) { ...@@ -491,8 +605,9 @@ TEST(ObserverListTest, StdIteratorRemoveBefore) {
EXPECT_EQ(-11, d.total); EXPECT_EQ(-11, d.total);
} }
TEST(ObserverListTest, StdIteratorRemoveAfter) { TYPED_TEST(ObserverListTest, StdIteratorRemoveAfter) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &c); Disrupter disrupter(&observer_list, &c);
...@@ -514,8 +629,9 @@ TEST(ObserverListTest, StdIteratorRemoveAfter) { ...@@ -514,8 +629,9 @@ TEST(ObserverListTest, StdIteratorRemoveAfter) {
EXPECT_EQ(-11, d.total); EXPECT_EQ(-11, d.total);
} }
TEST(ObserverListTest, StdIteratorRemoveAfterFront) { TYPED_TEST(ObserverListTest, StdIteratorRemoveAfterFront) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &a); Disrupter disrupter(&observer_list, &a);
...@@ -537,8 +653,9 @@ TEST(ObserverListTest, StdIteratorRemoveAfterFront) { ...@@ -537,8 +653,9 @@ TEST(ObserverListTest, StdIteratorRemoveAfterFront) {
EXPECT_EQ(-11, d.total); EXPECT_EQ(-11, d.total);
} }
TEST(ObserverListTest, StdIteratorRemoveBeforeBack) { TYPED_TEST(ObserverListTest, StdIteratorRemoveBeforeBack) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &d); Disrupter disrupter(&observer_list, &d);
...@@ -560,9 +677,10 @@ TEST(ObserverListTest, StdIteratorRemoveBeforeBack) { ...@@ -560,9 +677,10 @@ TEST(ObserverListTest, StdIteratorRemoveBeforeBack) {
EXPECT_EQ(0, d.total); EXPECT_EQ(0, d.total);
} }
TEST(ObserverListTest, StdIteratorRemoveFront) { TYPED_TEST(ObserverListTest, StdIteratorRemoveFront) {
using FooList = ObserverList<Foo>; DECLARE_TYPES;
FooList observer_list; using iterator = typename TestFixture::iterator;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true); Disrupter disrupter(&observer_list, true);
...@@ -573,12 +691,12 @@ TEST(ObserverListTest, StdIteratorRemoveFront) { ...@@ -573,12 +691,12 @@ TEST(ObserverListTest, StdIteratorRemoveFront) {
observer_list.AddObserver(&d); observer_list.AddObserver(&d);
bool test_disruptor = true; bool test_disruptor = true;
for (FooList::iterator i = observer_list.begin(), e = observer_list.end(); for (iterator i = observer_list.begin(), e = observer_list.end(); i != e;
i != e; ++i) { ++i) {
i->Observe(1); i->Observe(1);
// Check that second call to i->Observe() would crash here. // Check that second call to i->Observe() would crash here.
if (test_disruptor) { if (test_disruptor) {
EXPECT_FALSE(i.GetCurrent()); EXPECT_FALSE(this->GetCurrent(&i));
test_disruptor = false; test_disruptor = false;
} }
} }
...@@ -592,8 +710,9 @@ TEST(ObserverListTest, StdIteratorRemoveFront) { ...@@ -592,8 +710,9 @@ TEST(ObserverListTest, StdIteratorRemoveFront) {
EXPECT_EQ(-11, d.total); EXPECT_EQ(-11, d.total);
} }
TEST(ObserverListTest, StdIteratorRemoveBack) { TYPED_TEST(ObserverListTest, StdIteratorRemoveBack) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true); Disrupter disrupter(&observer_list, true);
...@@ -615,8 +734,9 @@ TEST(ObserverListTest, StdIteratorRemoveBack) { ...@@ -615,8 +734,9 @@ TEST(ObserverListTest, StdIteratorRemoveBack) {
EXPECT_EQ(-11, d.total); EXPECT_EQ(-11, d.total);
} }
TEST(ObserverListTest, NestedLoop) { TYPED_TEST(ObserverListTest, NestedLoop) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1); Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true); Disrupter disrupter(&observer_list, true);
...@@ -639,8 +759,9 @@ TEST(ObserverListTest, NestedLoop) { ...@@ -639,8 +759,9 @@ TEST(ObserverListTest, NestedLoop) {
EXPECT_EQ(-15, d.total); EXPECT_EQ(-15, d.total);
} }
TEST(ObserverListTest, NonCompactList) { TYPED_TEST(ObserverListTest, NonCompactList) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1); Adder a(1), b(-1);
Disrupter disrupter1(&observer_list, true); Disrupter disrupter1(&observer_list, true);
...@@ -667,8 +788,9 @@ TEST(ObserverListTest, NonCompactList) { ...@@ -667,8 +788,9 @@ TEST(ObserverListTest, NonCompactList) {
EXPECT_EQ(-13, b.total); EXPECT_EQ(-13, b.total);
} }
TEST(ObserverListTest, BecomesEmptyThanNonEmpty) { TYPED_TEST(ObserverListTest, BecomesEmptyThanNonEmpty) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1); Adder a(1), b(-1);
Disrupter disrupter1(&observer_list, true); Disrupter disrupter1(&observer_list, true);
...@@ -699,9 +821,11 @@ TEST(ObserverListTest, BecomesEmptyThanNonEmpty) { ...@@ -699,9 +821,11 @@ TEST(ObserverListTest, BecomesEmptyThanNonEmpty) {
EXPECT_EQ(-12, b.total); EXPECT_EQ(-12, b.total);
} }
TEST(ObserverListTest, AddObserverInTheLastObserve) { TYPED_TEST(ObserverListTest, AddObserverInTheLastObserve) {
ObserverList<Foo> observer_list; DECLARE_TYPES;
AddInObserve a(&observer_list); ObserverListFoo observer_list;
AddInObserve<ObserverListFoo> a(&observer_list);
Adder b(-1); Adder b(-1);
a.SetToAdd(&b); a.SetToAdd(&b);
...@@ -731,11 +855,12 @@ class MockLogAssertHandler { ...@@ -731,11 +855,12 @@ class MockLogAssertHandler {
}; };
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
TEST(ObserverListTest, NonReentrantObserverList) { TYPED_TEST(ObserverListTest, NonReentrantObserverList) {
using ::testing::_; DECLARE_TYPES;
using NonReentrantObserverListFoo = typename PickObserverList<
ObserverList<Foo, /*check_empty=*/false, /*allow_reentrancy=*/false> Foo>::template ObserverListType<Foo, /*check_empty=*/false,
non_reentrant_observer_list; /*allow_reentrancy=*/false>;
NonReentrantObserverListFoo non_reentrant_observer_list;
Adder a(1); Adder a(1);
non_reentrant_observer_list.AddObserver(&a); non_reentrant_observer_list.AddObserver(&a);
...@@ -749,10 +874,12 @@ TEST(ObserverListTest, NonReentrantObserverList) { ...@@ -749,10 +874,12 @@ TEST(ObserverListTest, NonReentrantObserverList) {
}); });
} }
TEST(ObserverListTest, ReentrantObserverList) { TYPED_TEST(ObserverListTest, ReentrantObserverList) {
using ::testing::_; DECLARE_TYPES;
using ReentrantObserverListFoo = typename PickObserverList<
ReentrantObserverList<Foo> reentrant_observer_list; Foo>::template ObserverListType<Foo, /*check_empty=*/false,
/*allow_reentrancy=*/true>;
ReentrantObserverListFoo reentrant_observer_list;
Adder a(1); Adder a(1);
reentrant_observer_list.AddObserver(&a); reentrant_observer_list.AddObserver(&a);
bool passed = false; bool passed = false;
...@@ -767,4 +894,119 @@ TEST(ObserverListTest, ReentrantObserverList) { ...@@ -767,4 +894,119 @@ TEST(ObserverListTest, ReentrantObserverList) {
} }
#endif #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 } // namespace base
// Copyright 2018 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.
// This is a "No Compile Test" suite.
// http://dev.chromium.org/developers/testing/no-compile-tests
#include <type_traits>
#include "base/observer_list.h"
namespace base {
#if defined(NCTEST_CHECKED_OBSERVER_USING_UNCHECKED_LIST) // [r"fatal error: static_assert failed due to requirement '!std::is_base_of<CheckedObserver, Observer>::value' \"CheckedObserver classes must not use ObserverList<T>::Unchecked.\""]
void WontCompile() {
struct Observer : public CheckedObserver {
void OnObserve() {}
};
ObserverList<Observer>::Unchecked list;
for (auto& observer: list)
observer.OnObserve();
}
#elif defined(NCTEST_UNCHECKED_OBSERVER_USING_CHECKED_LIST) // [r"fatal error: static_assert failed due to requirement 'std::is_base_of<CheckedObserver, UncheckedObserver>::value' \"Observers should inherit from base::CheckedObserver. Use ObserverList<T>::Unchecked to observe with raw pointers.\""]
void WontCompile() {
struct UncheckedObserver {
void OnObserve() {}
};
ObserverList<UncheckedObserver> list;
for (auto& observer: list)
observer.OnObserve();
}
#endif
} // namespace base
...@@ -41,6 +41,29 @@ ...@@ -41,6 +41,29 @@
#endif #endif
// DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) // DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
// As above, but for CHECK().
#if defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
// Official builds will CHECK, but also eat stream parameters. So match "".
#if defined(OFFICIAL_BUILD) && defined(NDEBUG)
#define EXPECT_CHECK_DEATH(statement) EXPECT_DEATH(statement, "")
#define ASSERT_CHECK_DEATH(statement) ASSERT_DEATH(statement, "")
#else
#define EXPECT_CHECK_DEATH(statement) EXPECT_DEATH(statement, "Check failed")
#define ASSERT_CHECK_DEATH(statement) ASSERT_DEATH(statement, "Check failed")
#endif // defined(OFFICIAL_BUILD) && defined(NDEBUG)
#else // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
// Note GTEST_UNSUPPORTED_DEATH_TEST takes a |regex| only to see whether it is a
// valid regex. It is never evaluated.
#define EXPECT_CHECK_DEATH(statement) \
GTEST_UNSUPPORTED_DEATH_TEST(statement, "", )
#define ASSERT_CHECK_DEATH(statement) \
GTEST_UNSUPPORTED_DEATH_TEST(statement, "", return )
#endif // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
namespace base { namespace base {
class FilePath; class FilePath;
......
...@@ -886,7 +886,7 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, ...@@ -886,7 +886,7 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate,
internal::NativeWidgetPrivate* native_widget_; internal::NativeWidgetPrivate* native_widget_;
base::ObserverList<WidgetObserver>::Unchecked observers_; base::ObserverList<WidgetObserver> observers_;
base::ObserverList<WidgetRemovalsObserver>::Unchecked removals_observers_; base::ObserverList<WidgetRemovalsObserver>::Unchecked removals_observers_;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef UI_VIEWS_WIDGET_WIDGET_OBSERVER_H_ #ifndef UI_VIEWS_WIDGET_WIDGET_OBSERVER_H_
#define UI_VIEWS_WIDGET_WIDGET_OBSERVER_H_ #define UI_VIEWS_WIDGET_WIDGET_OBSERVER_H_
#include "base/observer_list_types.h"
#include "ui/views/views_export.h" #include "ui/views/views_export.h"
namespace gfx { namespace gfx {
...@@ -16,7 +17,7 @@ namespace views { ...@@ -16,7 +17,7 @@ namespace views {
class Widget; class Widget;
// Observers can listen to various events on the Widgets. // Observers can listen to various events on the Widgets.
class VIEWS_EXPORT WidgetObserver { class VIEWS_EXPORT WidgetObserver : public base::CheckedObserver {
public: public:
// The closing notification is sent immediately in response to (i.e. in the // The closing notification is sent immediately in response to (i.e. in the
// same call stack as) a request to close the Widget (via Close() or // same call stack as) a request to close the Widget (via Close() or
...@@ -46,7 +47,7 @@ class VIEWS_EXPORT WidgetObserver { ...@@ -46,7 +47,7 @@ class VIEWS_EXPORT WidgetObserver {
const gfx::Rect& new_bounds) {} const gfx::Rect& new_bounds) {}
protected: protected:
virtual ~WidgetObserver() {} ~WidgetObserver() override {}
}; };
} // namespace views } // namespace views
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment