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") {
"cpu.h",
"critical_closure.h",
"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,
# but this target does not depend on "debug/allocator".
......@@ -2792,6 +2796,7 @@ if (enable_nocompile_tests) {
"memory/weak_ptr_unittest.nc",
"metrics/field_trial_params_unittest.nc",
"metrics/histogram_unittest.nc",
"observer_list_unittest.nc",
"optional_unittest.nc",
"strings/string16_unittest.nc",
"task/task_traits_extension_unittest.nc",
......
......@@ -268,6 +268,11 @@ class WeakPtr : public internal::WeakPtrBase {
// instance isn't being re-assigned or reset() racily with this call.
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:
friend class internal::SupportsWeakPtrBase;
template <typename U> friend class WeakPtr;
......
......@@ -392,6 +392,49 @@ TEST(WeakPtrTest, InvalidateWeakPtrs) {
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) {
int data;
WeakPtrFactory<int> factory(&data);
......
......@@ -17,6 +17,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list_internal.h"
#include "base/stl_util.h"
///////////////////////////////////////////////////////////////////////////////
......@@ -94,16 +95,24 @@ enum class ObserverListPolicy {
// TODO(oshima): Change the default to non reentrant. https://crbug.com/812109
template <class ObserverType,
bool check_empty = false,
bool allow_reentrancy = true>
class ObserverList
: public SupportsWeakPtr<
ObserverList<ObserverType, check_empty, allow_reentrancy>> {
bool allow_reentrancy = true,
class ObserverStorageType = internal::CheckedObserverAdapter>
class ObserverList : public SupportsWeakPtr<ObserverList<ObserverType,
check_empty,
allow_reentrancy,
ObserverStorageType>> {
public:
// Temporary type alias for introducing base::CheckedObserver. Existing
// ObserverLists will be Unchecked during the migration.
// TODO(https://crbug.com/842987): Use the Unchecked storage type when that
// template param is added.
using Unchecked = ObserverList<ObserverType, check_empty, allow_reentrancy>;
// Allow declaring an ObserverList<...>::Unchecked that replaces the default
// ObserverStorageType to use raw pointers. This is required to support legacy
// observers that do not inherit from CheckedObserver. The majority of NEW
// CODE SHOULD NOT USE THIS, but it may be suited for performance-critical
// 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.
class Iter {
......@@ -187,21 +196,23 @@ class ObserverList
}
private:
FRIEND_TEST_ALL_PREFIXES(ObserverListTest, BasicStdIterator);
FRIEND_TEST_ALL_PREFIXES(ObserverListTest, StdIteratorRemoveFront);
friend class ObserverListTestBase;
ObserverType* GetCurrent() const {
DCHECK(list_);
DCHECK_LT(index_, clamped_max_index());
return list_->observers_[index_];
return ObserverStorageType::template Get<ObserverType>(
list_->observers_[index_]);
}
void EnsureValidIndex() {
DCHECK(list_);
const size_t max_index = clamped_max_index();
while (index_ < max_index && !list_->observers_[index_])
while (index_ < max_index &&
list_->observers_[index_].IsMarkedForRemoval()) {
++index_;
}
}
size_t clamped_max_index() const {
return std::min(max_index_, list_->observers_.size());
......@@ -220,6 +231,7 @@ class ObserverList
using iterator = Iter;
using const_iterator = Iter;
using value_type = ObserverType;
const_iterator begin() const {
// An optimization: do not involve weak pointers for empty list.
......@@ -249,20 +261,22 @@ class ObserverList
NOTREACHED() << "Observers can only be added once!";
return;
}
observers_.push_back(obs);
observers_.emplace_back(ObserverStorageType(obs));
}
// Removes the given observer from this list. Does nothing if this observer is
// not in this list.
void RemoveObserver(const ObserverType* 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())
return;
DCHECK_GE(live_iterator_count_, 0);
if (live_iterator_count_) {
*it = nullptr;
it->MarkForRemoval();
} else {
observers_.erase(it);
}
......@@ -270,14 +284,22 @@ class ObserverList
// Determine whether a particular observer is in the list.
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.
void Clear() {
DCHECK_GE(live_iterator_count_, 0);
if (live_iterator_count_) {
std::fill(observers_.begin(), observers_.end(), nullptr);
for (auto& observer : observers_)
observer.MarkForRemoval();
} else {
observers_.clear();
}
......@@ -286,13 +308,12 @@ class ObserverList
bool might_have_observers() const { return !observers_.empty(); }
private:
// Compacts list of observers by removing null pointers.
// Compacts list of observers by removing those marked for removal.
void Compact() {
observers_.erase(std::remove(observers_.begin(), observers_.end(), nullptr),
observers_.end());
EraseIf(observers_, [](const auto& o) { return o.IsMarkedForRemoval(); });
}
std::vector<ObserverType*> observers_;
std::vector<ObserverStorageType> observers_;
// 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 @@
namespace base {
namespace {
class Foo {
class CheckedBase : public CheckedObserver {
public:
virtual void Observe(int x) = 0;
virtual ~Foo() = default;
~CheckedBase() override = default;
virtual int GetValue() const { return 0; }
};
class Adder : public Foo {
class UncheckedBase {
public:
explicit Adder(int scaler) : total(0), scaler_(scaler) {}
~Adder() override = default;
virtual void Observe(int x) = 0;
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_; }
int GetValue() const override { return total; }
......@@ -34,16 +65,18 @@ class Adder : public Foo {
int scaler_;
};
class Disrupter : public Foo {
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class DisrupterT : public Foo {
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) {}
Disrupter(ObserverList<Foo>* list, Foo* doomed)
: Disrupter(list, doomed, false) {}
Disrupter(ObserverList<Foo>* list, bool remove_self)
: Disrupter(list, nullptr, remove_self) {}
DisrupterT(ObserverListType* list, Foo* doomed)
: DisrupterT(list, doomed, false) {}
DisrupterT(ObserverListType* list, bool remove_self)
: DisrupterT(list, nullptr, remove_self) {}
~Disrupter() override = default;
~DisrupterT() override = default;
void Observe(int x) override {
if (remove_self_)
......@@ -55,14 +88,16 @@ class Disrupter : public Foo {
void SetDoomed(Foo* doomed) { doomed_ = doomed; }
private:
ObserverList<Foo>* list_;
ObserverListType* list_;
Foo* doomed_;
bool remove_self_;
};
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class AddInObserve : public Foo {
public:
explicit AddInObserve(ObserverList<Foo>* observer_list)
explicit AddInObserve(ObserverListType* observer_list)
: observer_list(observer_list), to_add_() {}
void SetToAdd(Foo* to_add) { to_add_ = to_add; }
......@@ -74,24 +109,89 @@ class AddInObserve : public Foo {
}
}
ObserverList<Foo>* observer_list;
ObserverListType* observer_list;
Foo* to_add_;
};
} // namespace
TEST(ObserverListTest, BasicTest) {
ObserverList<Foo> observer_list;
const ObserverList<Foo>& const_observer_list = observer_list;
class ObserverListTestBase {
public:
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());
// Iterator copy.
const ObserverList<Foo>::const_iterator it2 = it1;
const const_iterator it2 = it1;
EXPECT_EQ(it2, it1);
// Iterator assignment.
ObserverList<Foo>::const_iterator it3;
const_iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
......@@ -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());
// Iterator copy.
const ObserverList<Foo>::iterator it2 = it1;
const iterator it2 = it1;
EXPECT_EQ(it2, it1);
// Iterator assignment.
ObserverList<Foo>::iterator it3;
iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
......@@ -128,14 +228,14 @@ TEST(ObserverListTest, BasicTest) {
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());
// Iterator copy.
const ObserverList<Foo>::const_iterator it2 = it1;
const const_iterator it2 = it1;
EXPECT_EQ(it2, it1);
EXPECT_NE(it2, const_observer_list.end());
// Iterator assignment.
ObserverList<Foo>::const_iterator it3;
const_iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
......@@ -144,21 +244,21 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Iterator post increment.
ObserverList<Foo>::const_iterator it4 = it3++;
const_iterator it4 = it3++;
EXPECT_EQ(it4, it1);
EXPECT_EQ(it4, it2);
EXPECT_NE(it4, it3);
}
{
const ObserverList<Foo>::iterator it1 = observer_list.begin();
const iterator it1 = observer_list.begin();
EXPECT_NE(it1, observer_list.end());
// Iterator copy.
const ObserverList<Foo>::iterator it2 = it1;
const iterator it2 = it1;
EXPECT_EQ(it2, it1);
EXPECT_NE(it2, observer_list.end());
// Iterator assignment.
ObserverList<Foo>::iterator it3;
iterator it3;
it3 = it2;
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
......@@ -167,7 +267,7 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(it3, it1);
EXPECT_EQ(it3, it2);
// Iterator post increment.
ObserverList<Foo>::iterator it4 = it3++;
iterator it4 = it3++;
EXPECT_EQ(it4, it1);
EXPECT_EQ(it4, it2);
EXPECT_NE(it4, it3);
......@@ -193,9 +293,13 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(0, e.total);
}
TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
ObserverList<const Foo> ol;
const ObserverList<const Foo>& col = ol;
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);
......@@ -209,7 +313,7 @@ TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
EXPECT_TRUE(col.might_have_observers());
using It = ObserverList<const Foo>::const_iterator;
using It = typename ObserverListConstFoo::const_iterator;
{
It it = col.begin();
......@@ -263,8 +367,9 @@ TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
EXPECT_FALSE(col.might_have_observers());
}
TEST(ObserverListTest, DisruptSelf) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, DisruptSelf) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter evil(&observer_list, true);
......@@ -287,8 +392,9 @@ TEST(ObserverListTest, DisruptSelf) {
EXPECT_EQ(-10, d.total);
}
TEST(ObserverListTest, DisruptBefore) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, DisruptBefore) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter evil(&observer_list, &b);
......@@ -309,10 +415,11 @@ TEST(ObserverListTest, DisruptBefore) {
EXPECT_EQ(-20, d.total);
}
TEST(ObserverListTest, Existing) {
ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY);
TYPED_TEST(ObserverListTest, Existing) {
DECLARE_TYPES;
ObserverListFoo observer_list(ObserverListPolicy::EXISTING_ONLY);
Adder a(1);
AddInObserve b(&observer_list);
AddInObserve<ObserverListFoo> b(&observer_list);
Adder c(1);
b.SetToAdd(&c);
......@@ -333,9 +440,11 @@ TEST(ObserverListTest, Existing) {
EXPECT_EQ(1, c.total);
}
template <class ObserverListType,
class Foo = typename ObserverListType::value_type>
class AddInClearObserve : public Foo {
public:
explicit AddInClearObserve(ObserverList<Foo>* list)
explicit AddInClearObserve(ObserverListType* list)
: list_(list), added_(false), adder_(1) {}
void Observe(int /* x */) override {
......@@ -345,18 +454,19 @@ class AddInClearObserve : public Foo {
}
bool added() const { return added_; }
const Adder& adder() const { return adder_; }
const AdderT<Foo>& adder() const { return adder_; }
private:
ObserverList<Foo>* const list_;
ObserverListType* const list_;
bool added_;
Adder adder_;
AdderT<Foo> adder_;
};
TEST(ObserverListTest, ClearNotifyAll) {
ObserverList<Foo> observer_list;
AddInClearObserve a(&observer_list);
TYPED_TEST(ObserverListTest, ClearNotifyAll) {
DECLARE_TYPES;
ObserverListFoo observer_list;
AddInClearObserve<ObserverListFoo> a(&observer_list);
observer_list.AddObserver(&a);
......@@ -367,9 +477,10 @@ TEST(ObserverListTest, ClearNotifyAll) {
<< "Adder should observe once and have sum of 1.";
}
TEST(ObserverListTest, ClearNotifyExistingOnly) {
ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY);
AddInClearObserve a(&observer_list);
TYPED_TEST(ObserverListTest, ClearNotifyExistingOnly) {
DECLARE_TYPES;
ObserverListFoo observer_list(ObserverListPolicy::EXISTING_ONLY);
AddInClearObserve<ObserverListFoo> a(&observer_list);
observer_list.AddObserver(&a);
......@@ -380,21 +491,23 @@ TEST(ObserverListTest, ClearNotifyExistingOnly) {
<< "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(ObserverList<Foo>* list) : list_(list) {}
explicit ListDestructor(ObserverListType* list) : list_(list) {}
~ListDestructor() override = default;
void Observe(int x) override { delete list_; }
private:
ObserverList<Foo>* list_;
ObserverListType* list_;
};
TEST(ObserverListTest, IteratorOutlivesList) {
ObserverList<Foo>* observer_list = new ObserverList<Foo>;
ListDestructor a(observer_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)
......@@ -405,14 +518,14 @@ TEST(ObserverListTest, IteratorOutlivesList) {
// this test has failed. See http://crbug.com/85296.
}
TEST(ObserverListTest, BasicStdIterator) {
using FooList = ObserverList<Foo>;
FooList observer_list;
TYPED_TEST(ObserverListTest, BasicStdIterator) {
DECLARE_TYPES;
ObserverListFoo observer_list;
// An optimization: begin() and end() do not involve weak pointers on
// empty list.
EXPECT_FALSE(observer_list.begin().list_);
EXPECT_FALSE(observer_list.end().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)
......@@ -425,8 +538,7 @@ TEST(ObserverListTest, BasicStdIterator) {
observer_list.AddObserver(&c);
observer_list.AddObserver(&d);
for (FooList::iterator i = observer_list.begin(), e = observer_list.end();
i != e; ++i)
for (iterator i = observer_list.begin(), e = observer_list.end(); i != e; ++i)
i->Observe(1);
EXPECT_EQ(1, a.total);
......@@ -435,9 +547,9 @@ TEST(ObserverListTest, BasicStdIterator) {
EXPECT_EQ(-1, d.total);
// Check an iteration over a 'const view' for a given container.
const FooList& const_list = observer_list;
for (FooList::const_iterator i = const_list.begin(), e = const_list.end();
i != e; ++i) {
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()));
}
......@@ -445,8 +557,9 @@ TEST(ObserverListTest, BasicStdIterator) {
EXPECT_EQ(1, std::abs(o.GetValue()));
}
TEST(ObserverListTest, StdIteratorRemoveItself) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, StdIteratorRemoveItself) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
......@@ -468,8 +581,9 @@ TEST(ObserverListTest, StdIteratorRemoveItself) {
EXPECT_EQ(-11, d.total);
}
TEST(ObserverListTest, StdIteratorRemoveBefore) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, StdIteratorRemoveBefore) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &b);
......@@ -491,8 +605,9 @@ TEST(ObserverListTest, StdIteratorRemoveBefore) {
EXPECT_EQ(-11, d.total);
}
TEST(ObserverListTest, StdIteratorRemoveAfter) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, StdIteratorRemoveAfter) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &c);
......@@ -514,8 +629,9 @@ TEST(ObserverListTest, StdIteratorRemoveAfter) {
EXPECT_EQ(-11, d.total);
}
TEST(ObserverListTest, StdIteratorRemoveAfterFront) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, StdIteratorRemoveAfterFront) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &a);
......@@ -537,8 +653,9 @@ TEST(ObserverListTest, StdIteratorRemoveAfterFront) {
EXPECT_EQ(-11, d.total);
}
TEST(ObserverListTest, StdIteratorRemoveBeforeBack) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, StdIteratorRemoveBeforeBack) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, &d);
......@@ -560,9 +677,10 @@ TEST(ObserverListTest, StdIteratorRemoveBeforeBack) {
EXPECT_EQ(0, d.total);
}
TEST(ObserverListTest, StdIteratorRemoveFront) {
using FooList = ObserverList<Foo>;
FooList observer_list;
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);
......@@ -573,12 +691,12 @@ TEST(ObserverListTest, StdIteratorRemoveFront) {
observer_list.AddObserver(&d);
bool test_disruptor = true;
for (FooList::iterator i = observer_list.begin(), e = observer_list.end();
i != e; ++i) {
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(i.GetCurrent());
EXPECT_FALSE(this->GetCurrent(&i));
test_disruptor = false;
}
}
......@@ -592,8 +710,9 @@ TEST(ObserverListTest, StdIteratorRemoveFront) {
EXPECT_EQ(-11, d.total);
}
TEST(ObserverListTest, StdIteratorRemoveBack) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, StdIteratorRemoveBack) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
......@@ -615,8 +734,9 @@ TEST(ObserverListTest, StdIteratorRemoveBack) {
EXPECT_EQ(-11, d.total);
}
TEST(ObserverListTest, NestedLoop) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, NestedLoop) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1), c(1), d(-1);
Disrupter disrupter(&observer_list, true);
......@@ -639,8 +759,9 @@ TEST(ObserverListTest, NestedLoop) {
EXPECT_EQ(-15, d.total);
}
TEST(ObserverListTest, NonCompactList) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, NonCompactList) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1);
Disrupter disrupter1(&observer_list, true);
......@@ -667,8 +788,9 @@ TEST(ObserverListTest, NonCompactList) {
EXPECT_EQ(-13, b.total);
}
TEST(ObserverListTest, BecomesEmptyThanNonEmpty) {
ObserverList<Foo> observer_list;
TYPED_TEST(ObserverListTest, BecomesEmptyThanNonEmpty) {
DECLARE_TYPES;
ObserverListFoo observer_list;
Adder a(1), b(-1);
Disrupter disrupter1(&observer_list, true);
......@@ -699,9 +821,11 @@ TEST(ObserverListTest, BecomesEmptyThanNonEmpty) {
EXPECT_EQ(-12, b.total);
}
TEST(ObserverListTest, AddObserverInTheLastObserve) {
ObserverList<Foo> observer_list;
AddInObserve a(&observer_list);
TYPED_TEST(ObserverListTest, AddObserverInTheLastObserve) {
DECLARE_TYPES;
ObserverListFoo observer_list;
AddInObserve<ObserverListFoo> a(&observer_list);
Adder b(-1);
a.SetToAdd(&b);
......@@ -731,11 +855,12 @@ class MockLogAssertHandler {
};
#if DCHECK_IS_ON()
TEST(ObserverListTest, NonReentrantObserverList) {
using ::testing::_;
ObserverList<Foo, /*check_empty=*/false, /*allow_reentrancy=*/false>
non_reentrant_observer_list;
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);
......@@ -749,10 +874,12 @@ TEST(ObserverListTest, NonReentrantObserverList) {
});
}
TEST(ObserverListTest, ReentrantObserverList) {
using ::testing::_;
ReentrantObserverList<Foo> reentrant_observer_list;
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;
......@@ -767,4 +894,119 @@ TEST(ObserverListTest, ReentrantObserverList) {
}
#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
// 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 @@
#endif
// 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 {
class FilePath;
......
......@@ -886,7 +886,7 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate,
internal::NativeWidgetPrivate* native_widget_;
base::ObserverList<WidgetObserver>::Unchecked observers_;
base::ObserverList<WidgetObserver> observers_;
base::ObserverList<WidgetRemovalsObserver>::Unchecked removals_observers_;
......
......@@ -5,6 +5,7 @@
#ifndef 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"
namespace gfx {
......@@ -16,7 +17,7 @@ namespace views {
class Widget;
// Observers can listen to various events on the Widgets.
class VIEWS_EXPORT WidgetObserver {
class VIEWS_EXPORT WidgetObserver : public base::CheckedObserver {
public:
// 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
......@@ -46,7 +47,7 @@ class VIEWS_EXPORT WidgetObserver {
const gfx::Rect& new_bounds) {}
protected:
virtual ~WidgetObserver() {}
~WidgetObserver() override {}
};
} // 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