// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/memory/weak_auto_reset.h" #include #include "base/memory/weak_ptr.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace { template class HasWeakFactory { public: HasWeakFactory() = default; ~HasWeakFactory() = default; // Returns a WeakAutoReset that temporarily sets value_ to `value`. auto SetValueScoped(T value) { return WeakAutoReset(factory_.GetWeakPtr(), &HasWeakFactory::value_, std::move(value)); } void set_value(T value) { value_ = std::move(value); } const T& value() const { return value_; } WeakPtr GetWeakPtr() { return factory_.GetWeakPtr(); } private: T value_ = T(); WeakPtrFactory factory_{this}; }; } // namespace TEST(WeakAutoResetTest, DefaultConstructor) { WeakAutoReset, int> empty; } TEST(WeakAutoResetTest, SingleAutoReset) { HasWeakFactory hwf; { WeakAutoReset reset = hwf.SetValueScoped(1); EXPECT_EQ(1, hwf.value()); } EXPECT_EQ(0, hwf.value()); } TEST(WeakAutoResetTest, SingleAutoResetObjectDestroyed) { auto hwf = std::make_unique>(); WeakAutoReset reset = hwf->SetValueScoped(1); EXPECT_EQ(1, hwf->value()); hwf.reset(); // ASAN will crash here if we don't correctly detect that hwf has gone away. } TEST(WeakAutoResetTest, MultipleNested) { HasWeakFactory hwf; { WeakAutoReset reset = hwf.SetValueScoped(1); EXPECT_EQ(1, hwf.value()); { WeakAutoReset reset2 = hwf.SetValueScoped(2); EXPECT_EQ(2, hwf.value()); } EXPECT_EQ(1, hwf.value()); } EXPECT_EQ(0, hwf.value()); } TEST(WeakAutoResetTest, MultipleNestedObjectDestroyed) { auto hwf = std::make_unique>(); WeakAutoReset reset = hwf->SetValueScoped(1); EXPECT_EQ(1, hwf->value()); WeakAutoReset reset2 = hwf->SetValueScoped(2); EXPECT_EQ(2, hwf->value()); hwf.reset(); // ASAN will crash here if we don't correctly detect that hwf has gone away. } TEST(WeakAutoResetTest, MoveAssignmentTransfersOwnership) { HasWeakFactory hwf; // Create an auto-reset outside of a scope. WeakAutoReset reset = hwf.SetValueScoped(1); { WeakAutoReset, int> reset2; EXPECT_EQ(1, hwf.value()); // Move the auto-reset to an instance inside the scope. This should not // cause the value to reset. reset2 = std::move(reset); EXPECT_EQ(1, hwf.value()); } // Because the active auto-reset went away with the scope, the original value // should be restored. EXPECT_EQ(0, hwf.value()); } TEST(WeakAutoResetTest, MoveAssignmentResetsOldValue) { HasWeakFactory hwf1; HasWeakFactory hwf2; WeakAutoReset reset = hwf1.SetValueScoped(1); WeakAutoReset reset2 = hwf2.SetValueScoped(2); EXPECT_EQ(1, hwf1.value()); EXPECT_EQ(2, hwf2.value()); // Overwriting the first with the second should reset the first value, but not // the second. reset = std::move(reset2); EXPECT_EQ(0, hwf1.value()); EXPECT_EQ(2, hwf2.value()); // Overwriting the moved value with a default value should have no effect. reset2 = WeakAutoReset, int>(); // Overwriting the live auto-reset with a default value should reset the other // value. reset = WeakAutoReset, int>(); EXPECT_EQ(0, hwf1.value()); EXPECT_EQ(0, hwf2.value()); } TEST(WeakAutoResetTest, MoveAssignmentToSelfIsNoOp) { HasWeakFactory hwf; { WeakAutoReset reset = hwf.SetValueScoped(1); EXPECT_EQ(1, hwf.value()); // Move the auto-reset to itself. This should have no effect. We'll need to // create an intermediate so that we don't get a compile error. auto* const reset_ref = &reset; reset = std::move(*reset_ref); EXPECT_EQ(1, hwf.value()); } // The auto-reset goes out of scope, resetting the value. EXPECT_EQ(0, hwf.value()); } TEST(WeakAutoResetTest, DeleteTargetObjectAfterMoveIsSafe) { auto hwf = std::make_unique>(); WeakAutoReset reset = hwf->SetValueScoped(1); WeakAutoReset reset2 = std::move(reset); hwf.reset(); // ASAN will crash here if we don't correctly detect that hwf has gone away. } using HasWeakFactoryPointer = std::unique_ptr>; TEST(WeakAutoResetTest, TestSafelyMovesValue) { // We'll use an object that owns another object while keeping a weak reference // to the inner object to determine its lifetime. auto inner = std::make_unique>(); auto weak_ptr = inner->GetWeakPtr(); auto outer = std::make_unique>(); outer->set_value(std::move(inner)); ASSERT_TRUE(weak_ptr); { // Transfer ownership of the inner object to the auto-reset. WeakAutoReset reset = outer->SetValueScoped(HasWeakFactoryPointer()); EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); } // Transfer ownership back to the outer object. EXPECT_TRUE(weak_ptr); EXPECT_TRUE(outer->value()); // Destroying the outer object destroys the inner object. outer.reset(); EXPECT_FALSE(weak_ptr); } TEST(WeakAutoResetTest, TestSafelyMovesValueAndThenDestroysIt) { // We'll use an object that owns another object while keeping a weak reference // to the inner object to determine its lifetime. auto inner = std::make_unique>(); auto weak_ptr = inner->GetWeakPtr(); auto outer = std::make_unique>(); outer->set_value(std::move(inner)); ASSERT_TRUE(weak_ptr); { // Transfer ownership of the inner object to the auto-reset. WeakAutoReset reset = outer->SetValueScoped(HasWeakFactoryPointer()); EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); // Destroy the outer object. The auto-reset still owns the old inner object. outer.reset(); EXPECT_TRUE(weak_ptr); } // Onwership can't be transferred back so the inner object is destroyed. EXPECT_FALSE(weak_ptr); } TEST(WeakAutoResetTest, TestMoveConstructorMovesOldValue) { // We'll use an object that owns another object while keeping a weak reference // to the inner object to determine its lifetime. auto inner = std::make_unique>(); auto weak_ptr = inner->GetWeakPtr(); auto outer = std::make_unique>(); outer->set_value(std::move(inner)); ASSERT_TRUE(weak_ptr); { // Transfer ownership of the inner object to the auto-reset. WeakAutoReset reset = outer->SetValueScoped(HasWeakFactoryPointer()); EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); { // Move ownership of the old object to a new auto-reset. WeakAutoReset reset2(std::move(reset)); EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); } // Destroying the second auto-reset transfers ownership back to the outer // object. EXPECT_TRUE(weak_ptr); EXPECT_TRUE(outer->value()); } } TEST(WeakAutoResetTest, TestMoveAssignmentMovesOldValue) { // We'll use an object that owns another object while keeping a weak reference // to the inner object to determine its lifetime. auto inner = std::make_unique>(); auto weak_ptr = inner->GetWeakPtr(); auto outer = std::make_unique>(); outer->set_value(std::move(inner)); ASSERT_TRUE(weak_ptr); { // Create an auto-reset that will receive ownership later. WeakAutoReset, HasWeakFactoryPointer> reset; { // Move ownership of the inner object to an auto-reset. WeakAutoReset reset2 = outer->SetValueScoped(HasWeakFactoryPointer()); EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); // Transfer ownership to the other auto-reset. reset = std::move(reset2); EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); } // The auto-reset that initially received the value is gone, but the one // actually holding the value is still in scope. EXPECT_TRUE(weak_ptr); EXPECT_FALSE(outer->value()); } // Now both have gone out of scope, so the inner object should be returned to // the outer one. EXPECT_TRUE(weak_ptr); EXPECT_TRUE(outer->value()); } TEST(WeakAutoResetTest, TestOldAndNewValuesAreSwapped) { // We'll use an object that owns another object while keeping a weak reference // to the inner object to determine its lifetime. auto inner = std::make_unique>(); auto weak_ptr = inner->GetWeakPtr(); auto outer = std::make_unique>(); outer->set_value(std::move(inner)); ASSERT_TRUE(weak_ptr); // Create a second inner object that we'll swap with the first. auto replacement = std::make_unique>(); auto weak_ptr2 = replacement->GetWeakPtr(); { // Swap the values. WeakAutoReset reset = outer->SetValueScoped(std::move(replacement)); EXPECT_TRUE(weak_ptr); EXPECT_TRUE(weak_ptr2); EXPECT_EQ(weak_ptr2.get(), outer->value().get()); } // Unswap the values. The replacement is discarded. EXPECT_TRUE(weak_ptr); EXPECT_FALSE(weak_ptr2); EXPECT_EQ(weak_ptr.get(), outer->value().get()); } } // namespace base