| // Copyright 2017 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/threading/sequence_local_storage_map.h" | |
| #include <memory> | |
| #include <utility> | |
| #include "base/compiler_specific.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "partition_alloc/buildflags.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace base::internal { | |
| namespace { | |
| constexpr int kSlotId = 1; | |
| class TRIVIAL_ABI SetOnDestroy { | |
| public: | |
| explicit SetOnDestroy(bool* was_destroyed_ptr) | |
| : was_destroyed_ptr_(was_destroyed_ptr) { | |
| DCHECK(was_destroyed_ptr_); | |
| DCHECK(!(*was_destroyed_ptr_)); | |
| } | |
| SetOnDestroy(const SetOnDestroy&) = delete; | |
| SetOnDestroy& operator=(const SetOnDestroy&) = delete; | |
| SetOnDestroy(SetOnDestroy&& other) { | |
| using std::swap; | |
| swap(was_destroyed_ptr_, other.was_destroyed_ptr_); | |
| } | |
| SetOnDestroy& operator=(SetOnDestroy&& other) { | |
| using std::swap; | |
| swap(was_destroyed_ptr_, other.was_destroyed_ptr_); | |
| return *this; | |
| } | |
| ~SetOnDestroy() { | |
| if (!was_destroyed_ptr_) { | |
| return; | |
| } | |
| DCHECK(!(*was_destroyed_ptr_)); | |
| *was_destroyed_ptr_ = true; | |
| } | |
| private: | |
| #if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_INSTANCE_TRACER) | |
| // In instance tracer mode, raw_ptr is larger than a void*, but values stored | |
| // inline in a SequenceLocalStorageMap must be at most sizeof(void*). | |
| RAW_PTR_EXCLUSION bool* was_destroyed_ptr_; | |
| #else | |
| raw_ptr<bool> was_destroyed_ptr_; | |
| #endif | |
| }; | |
| template <typename T, typename... Args> | |
| SequenceLocalStorageMap::ValueDestructorPair CreateExternalValueDestructorPair( | |
| Args... args) { | |
| internal::SequenceLocalStorageMap::ExternalValue value; | |
| value.emplace(new T(args...)); | |
| auto* destructor = | |
| SequenceLocalStorageMap::MakeExternalDestructor<T, | |
| std::default_delete<T>>(); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair{ | |
| std::move(value), destructor}; | |
| return value_destructor_pair; | |
| } | |
| template <typename T, typename... Args> | |
| SequenceLocalStorageMap::ValueDestructorPair CreateInlineValueDestructorPair( | |
| Args... args) { | |
| internal::SequenceLocalStorageMap::InlineValue value; | |
| value.emplace<T>(args...); | |
| auto* destructor = SequenceLocalStorageMap::MakeInlineDestructor<T>(); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair{ | |
| std::move(value), destructor}; | |
| return value_destructor_pair; | |
| } | |
| } // namespace | |
| // Verify that setting a value in the SequenceLocalStorageMap, then getting | |
| // it will yield the same value. | |
| TEST(SequenceLocalStorageMapTest, SetGetExternal) { | |
| SequenceLocalStorageMap sequence_local_storage_map; | |
| ScopedSetSequenceLocalStorageMapForCurrentThread | |
| scoped_sequence_local_storage_map(&sequence_local_storage_map); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair = | |
| CreateExternalValueDestructorPair<int>(5); | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair)); | |
| EXPECT_EQ( | |
| sequence_local_storage_map.Get(kSlotId)->external_value.value_as<int>(), | |
| 5); | |
| } | |
| TEST(SequenceLocalStorageMapTest, SetGetInline) { | |
| SequenceLocalStorageMap sequence_local_storage_map; | |
| ScopedSetSequenceLocalStorageMapForCurrentThread | |
| scoped_sequence_local_storage_map(&sequence_local_storage_map); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair = | |
| CreateInlineValueDestructorPair<int>(5); | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair)); | |
| EXPECT_EQ( | |
| sequence_local_storage_map.Get(kSlotId)->inline_value.value_as<int>(), 5); | |
| } | |
| // Verify that the destructor is called on a value stored in the | |
| // SequenceLocalStorageMap when SequenceLocalStorageMap is destroyed. | |
| TEST(SequenceLocalStorageMapTest, DestructorExternal) { | |
| bool set_on_destruction = false; | |
| { | |
| SequenceLocalStorageMap sequence_local_storage_map; | |
| ScopedSetSequenceLocalStorageMapForCurrentThread | |
| scoped_sequence_local_storage_map(&sequence_local_storage_map); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair = | |
| CreateExternalValueDestructorPair<SetOnDestroy>(&set_on_destruction); | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair)); | |
| } | |
| EXPECT_TRUE(set_on_destruction); | |
| } | |
| // Verify that overwriting a value already in the SequenceLocalStorageMap | |
| // calls value's destructor. | |
| TEST(SequenceLocalStorageMapTest, DestructorCalledOnSetOverwriteExternal) { | |
| bool set_on_destruction = false; | |
| bool set_on_destruction2 = false; | |
| { | |
| SequenceLocalStorageMap sequence_local_storage_map; | |
| ScopedSetSequenceLocalStorageMapForCurrentThread | |
| scoped_sequence_local_storage_map(&sequence_local_storage_map); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair = | |
| CreateExternalValueDestructorPair<SetOnDestroy>(&set_on_destruction); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair2 = | |
| CreateExternalValueDestructorPair<SetOnDestroy>(&set_on_destruction2); | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair)); | |
| ASSERT_FALSE(set_on_destruction); | |
| // Overwrites the old value in the slot. | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair2)); | |
| // Destructor should've been called for the old value in the slot, and not | |
| // yet called for the new value. | |
| EXPECT_TRUE(set_on_destruction); | |
| EXPECT_FALSE(set_on_destruction2); | |
| } | |
| EXPECT_TRUE(set_on_destruction2); | |
| } | |
| template <typename T> | |
| void DestructorInline() { | |
| if constexpr (!absl::is_trivially_relocatable<T>()) { | |
| // Test disabled because there is no reliable way to detect SetOnDestroy | |
| // is trivially relocatble. | |
| // See https://github.com/llvm/llvm-project/issues/69394 | |
| GTEST_SKIP(); | |
| } else { | |
| bool set_on_destruction = false; | |
| { | |
| SequenceLocalStorageMap sequence_local_storage_map; | |
| ScopedSetSequenceLocalStorageMapForCurrentThread | |
| scoped_sequence_local_storage_map(&sequence_local_storage_map); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair = | |
| CreateInlineValueDestructorPair<T>(&set_on_destruction); | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair)); | |
| } | |
| EXPECT_TRUE(set_on_destruction); | |
| } | |
| } | |
| TEST(SequenceLocalStorageMapTest, DestructorInline) { | |
| DestructorInline<SetOnDestroy>(); | |
| } | |
| template <typename T> | |
| void DestructorCalledOnSetOverwriteInline() { | |
| if constexpr (!absl::is_trivially_relocatable<T>()) { | |
| // Test disabled because there is no reliable way to detect SetOnDestroy | |
| // is trivially relocatble. | |
| // See https://github.com/llvm/llvm-project/issues/69394 | |
| GTEST_SKIP(); | |
| } else { | |
| bool set_on_destruction = false; | |
| bool set_on_destruction2 = false; | |
| { | |
| SequenceLocalStorageMap sequence_local_storage_map; | |
| ScopedSetSequenceLocalStorageMapForCurrentThread | |
| scoped_sequence_local_storage_map(&sequence_local_storage_map); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair = | |
| CreateInlineValueDestructorPair<T>(&set_on_destruction); | |
| SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair2 = | |
| CreateInlineValueDestructorPair<T>(&set_on_destruction2); | |
| sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair)); | |
| ASSERT_FALSE(set_on_destruction); | |
| // Overwrites the old value in the slot. | |
| sequence_local_storage_map.Set(kSlotId, | |
| std::move(value_destructor_pair2)); | |
| // Destructor should've been called for the old value in the slot, and not | |
| // yet called for the new value. | |
| EXPECT_TRUE(set_on_destruction); | |
| EXPECT_FALSE(set_on_destruction2); | |
| } | |
| EXPECT_TRUE(set_on_destruction2); | |
| } | |
| } | |
| TEST(SequenceLocalStorageMapTest, DestructorCalledOnSetOverwriteInline) { | |
| DestructorCalledOnSetOverwriteInline<SetOnDestroy>(); | |
| } | |
| } // namespace base::internal |