From 769e1df659491df9951487fa467854793f9a676a Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jan 2025 00:49:56 -0500 Subject: [PATCH 01/84] Firestore: Fix memory leak in thread_safe_memoizer.h --- Firestore/core/src/core/composite_filter.cc | 6 +- Firestore/core/src/core/field_filter.cc | 7 ++- Firestore/core/src/core/filter.cc | 6 -- Firestore/core/src/core/filter.h | 4 +- Firestore/core/src/core/query.cc | 18 +++--- Firestore/core/src/core/query.h | 12 ++-- .../core/src/util/thread_safe_memoizer.h | 61 ++++++++++--------- .../unit/util/thread_safe_memoizer_test.cc | 3 +- 8 files changed, 56 insertions(+), 61 deletions(-) diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 7bbb81abc57..62f5da20d08 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -143,12 +143,12 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( const std::vector& CompositeFilter::Rep::GetFlattenedFilters() const { - return memoized_flattened_filters_->memoize([&]() { - std::vector flattened_filters; + return memoized_flattened_filters_.memoize([&]() { + auto flattened_filters = std::make_unique>(); for (const auto& filter : filters()) std::copy(filter.GetFlattenedFilters().begin(), filter.GetFlattenedFilters().end(), - std::back_inserter(flattened_filters)); + std::back_inserter(*flattened_filters)); return flattened_filters; }); } diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index b68956c8a52..171c7016e4a 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -124,9 +124,10 @@ FieldFilter::FieldFilter(std::shared_ptr rep) const std::vector& FieldFilter::Rep::GetFlattenedFilters() const { // This is already a field filter, so we return a vector of size one. - return memoized_flattened_filters_->memoize([&]() { - return std::vector{ - FieldFilter(std::make_shared(*this))}; + return memoized_flattened_filters_.memoize([&]() { + auto filters = std::make_unique>(); + filters->push_back(FieldFilter(std::make_shared(*this))); + return filters; }); } diff --git a/Firestore/core/src/core/filter.cc b/Firestore/core/src/core/filter.cc index a77ccc55e34..853c15b31ba 100644 --- a/Firestore/core/src/core/filter.cc +++ b/Firestore/core/src/core/filter.cc @@ -35,12 +35,6 @@ std::ostream& operator<<(std::ostream& os, const Filter& filter) { return os << filter.ToString(); } -Filter::Rep::Rep() - : memoized_flattened_filters_( - std::make_shared< - util::ThreadSafeMemoizer>>()) { -} - } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index ec20120daf6..17942439fe3 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -114,8 +114,6 @@ class Filter { protected: class Rep { public: - Rep(); - virtual ~Rep() = default; virtual Type type() const { @@ -160,7 +158,7 @@ class Filter { * (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag` * member variable, which is not copyable). */ - mutable std::shared_ptr>> + mutable util::ThreadSafeMemoizer> memoized_flattened_filters_; }; diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 5b70ebc322f..3d91aaf070d 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -92,9 +92,9 @@ absl::optional Query::FindOpInsideFilters( } const std::vector& Query::normalized_order_bys() const { - return memoized_normalized_order_bys_->memoize([&]() { + return memoized_normalized_order_bys_.memoize([&]() { // Any explicit order by fields should be added as is. - std::vector result = explicit_order_bys_; + auto result = std::make_unique>(explicit_order_bys_); std::set fieldsNormalized; for (const OrderBy& order_by : explicit_order_bys_) { fieldsNormalized.insert(order_by.field()); @@ -117,14 +117,14 @@ const std::vector& Query::normalized_order_bys() const { for (const model::FieldPath& field : inequality_fields) { if (fieldsNormalized.find(field) == fieldsNormalized.end() && !field.IsKeyFieldPath()) { - result.push_back(OrderBy(field, last_direction)); + result->push_back(OrderBy(field, last_direction)); } } // Add the document key field to the last if it is not explicitly ordered. if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == fieldsNormalized.end()) { - result.push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); + result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); } return result; @@ -297,13 +297,15 @@ std::string Query::ToString() const { } const Target& Query::ToTarget() const& { - return memoized_target_->memoize( - [&]() { return ToTarget(normalized_order_bys()); }); + return memoized_target_.memoize([&]() { + return std::make_unique(ToTarget(normalized_order_bys())); + }); } const Target& Query::ToAggregateTarget() const& { - return memoized_aggregate_target_->memoize( - [&]() { return ToTarget(explicit_order_bys_); }); + return memoized_aggregate_target_.memoize([&]() { + return std::make_unique(ToTarget(explicit_order_bys_)); + }); } Target Query::ToTarget(const std::vector& order_bys) const { diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index 23351a4de56..9f73cc5e120 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -295,20 +295,16 @@ class Query { // member variable, which is not copyable). // The memoized list of sort orders. - mutable std::shared_ptr>> - memoized_normalized_order_bys_{ - std::make_shared>>()}; + mutable util::ThreadSafeMemoizer> + memoized_normalized_order_bys_; // The corresponding Target of this Query instance. - mutable std::shared_ptr> memoized_target_{ - std::make_shared>()}; + mutable util::ThreadSafeMemoizer memoized_target_; // The corresponding aggregate Target of this Query instance. Unlike targets // for non-aggregate queries, aggregate query targets do not contain // normalized order-bys, they only contain explicit order-bys. - mutable std::shared_ptr> - memoized_aggregate_target_{ - std::make_shared>()}; + mutable util::ThreadSafeMemoizer memoized_aggregate_target_; }; bool operator==(const Query& lhs, const Query& rhs); diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 5bc6fc1c529..0ae090c00be 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -18,8 +18,7 @@ #define FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ #include -#include // NOLINT(build/c++11) -#include +#include namespace firebase { namespace firestore { @@ -36,43 +35,47 @@ class ThreadSafeMemoizer { public: ThreadSafeMemoizer() = default; - ~ThreadSafeMemoizer() { - // Call `std::call_once` in order to synchronize with the "active" - // invocation of `memoize()`. Without this synchronization, there is a data - // race between this destructor, which "reads" `memoized_value_` to destroy - // it, and the write to `memoized_value_` done by the "active" invocation of - // `memoize()`. - std::call_once(once_, [&]() {}); + ThreadSafeMemoizer(const ThreadSafeMemoizer&) { + } + + ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer&) { + return *this; } - // This class cannot be copied or moved, because it has `std::once_flag` - // member. - ThreadSafeMemoizer(const ThreadSafeMemoizer&) = delete; - ThreadSafeMemoizer(ThreadSafeMemoizer&&) = delete; - ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer&) = delete; - ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&&) = delete; + ThreadSafeMemoizer(ThreadSafeMemoizer&& other) = default; + ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) = default; + + ~ThreadSafeMemoizer() { + delete memoized_.load(); + } /** * Memoize a value. * - * The std::function object specified by the first invocation of this - * function (the "active" invocation) will be invoked synchronously. - * None of the std::function objects specified by the subsequent - * invocations of this function (the "passive" invocations) will be - * invoked. All invocations, both "active" and "passive", will return a - * reference to the std::vector created by copying the return value from - * the std::function specified by the "active" invocation. It is, - * therefore, the "active" invocation's job to return the std::vector - * to memoize. + * If there is no memoized value then the given function is called to create + * the value, returning a reference to the created value and storing the + * pointer to the created value. On the other hand, if there _is_ a memoized + * value from a previous invocation then a reference to that object is + * returned and the given function is not called. Note that the given function + * may be called more than once and, therefore, must be idempotent. */ - const T& memoize(std::function func) { - std::call_once(once_, [&]() { memoized_value_ = func(); }); - return memoized_value_; + const T& memoize(std::function()> func) { + while (true) { + T* old_memoized = memoized_.load(); + if (old_memoized) { + return *old_memoized; + } + + std::unique_ptr new_memoized = func(); + + if (memoized_.compare_exchange_strong(old_memoized, new_memoized.get())) { + return *new_memoized.release(); + } + } } private: - std::once_flag once_; - T memoized_value_; + std::atomic memoized_ = {nullptr}; }; } // namespace util diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 767fa7cb318..bfa77a02997 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -16,6 +16,7 @@ #include "Firestore/core/src/util/thread_safe_memoizer.h" +#include // NOLINT(build/c++11) #include // NOLINT(build/c++11) #include "gtest/gtest.h" @@ -32,7 +33,7 @@ TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { // If the lambda gets executed multiple times, threads will see incremented // `global_int`. global_int++; - return global_int.load(); + return std::make_unique(global_int.load()); }; const int num_threads = 5; From 110c0f0221f6a63e5f66b9264dcc5b0f41639b2e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jan 2025 00:54:15 -0500 Subject: [PATCH 02/84] Firestore/CHANGELOG.md updated --- Firestore/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index ee7ea12f87f..25441918775 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Fixed memory leak in `Query.whereField()`. (#13978) + # 11.6.0 - [fixed] Add conditional `Sendable` conformance so `ServerTimestamp` is `Sendable` if `T` is `Sendable`. (#14042) From 0e8bc9d7bf8411942261cdeca163c0212d81c871 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jan 2025 01:09:36 -0500 Subject: [PATCH 03/84] use absl::make_unique instead of std::make_unique --- Firestore/core/src/core/composite_filter.cc | 3 ++- Firestore/core/src/core/field_filter.cc | 3 ++- Firestore/core/src/core/query.cc | 7 ++++--- Firestore/core/test/unit/util/thread_safe_memoizer_test.cc | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 62f5da20d08..410945277f9 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -23,6 +23,7 @@ #include "Firestore/core/src/model/field_path.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/string_format.h" +#include "absl/memory/memory.h" #include "absl/strings/str_join.h" namespace firebase { @@ -144,7 +145,7 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( const std::vector& CompositeFilter::Rep::GetFlattenedFilters() const { return memoized_flattened_filters_.memoize([&]() { - auto flattened_filters = std::make_unique>(); + auto flattened_filters = absl::make_unique>(); for (const auto& filter : filters()) std::copy(filter.GetFlattenedFilters().begin(), filter.GetFlattenedFilters().end(), diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index 171c7016e4a..3a034f05d36 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -32,6 +32,7 @@ #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" #include "absl/algorithm/container.h" +#include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/types/optional.h" @@ -125,7 +126,7 @@ FieldFilter::FieldFilter(std::shared_ptr rep) const std::vector& FieldFilter::Rep::GetFlattenedFilters() const { // This is already a field filter, so we return a vector of size one. return memoized_flattened_filters_.memoize([&]() { - auto filters = std::make_unique>(); + auto filters = absl::make_unique>(); filters->push_back(FieldFilter(std::make_shared(*this))); return filters; }); diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 3d91aaf070d..96f997da96f 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -30,6 +30,7 @@ #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" #include "absl/algorithm/container.h" +#include "absl/memory/memory.h" #include "absl/strings/str_cat.h" namespace firebase { @@ -94,7 +95,7 @@ absl::optional Query::FindOpInsideFilters( const std::vector& Query::normalized_order_bys() const { return memoized_normalized_order_bys_.memoize([&]() { // Any explicit order by fields should be added as is. - auto result = std::make_unique>(explicit_order_bys_); + auto result = absl::make_unique>(explicit_order_bys_); std::set fieldsNormalized; for (const OrderBy& order_by : explicit_order_bys_) { fieldsNormalized.insert(order_by.field()); @@ -298,13 +299,13 @@ std::string Query::ToString() const { const Target& Query::ToTarget() const& { return memoized_target_.memoize([&]() { - return std::make_unique(ToTarget(normalized_order_bys())); + return absl::make_unique(ToTarget(normalized_order_bys())); }); } const Target& Query::ToAggregateTarget() const& { return memoized_aggregate_target_.memoize([&]() { - return std::make_unique(ToTarget(explicit_order_bys_)); + return absl::make_unique(ToTarget(explicit_order_bys_)); }); } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index bfa77a02997..9a634c215ba 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -16,8 +16,8 @@ #include "Firestore/core/src/util/thread_safe_memoizer.h" -#include // NOLINT(build/c++11) #include // NOLINT(build/c++11) +#include "absl/memory/memory.h" #include "gtest/gtest.h" namespace firebase { @@ -33,7 +33,7 @@ TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { // If the lambda gets executed multiple times, threads will see incremented // `global_int`. global_int++; - return std::make_unique(global_int.load()); + return absl::make_unique(global_int.load()); }; const int num_threads = 5; From 260de9f36b742f51cb5529149a283fa6b212c263 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jan 2025 01:16:20 -0500 Subject: [PATCH 04/84] upgrade python from 3.7 to 3.11 in firestore.yml and firestore-nightly.yml --- .github/workflows/firestore-nightly.yml | 2 +- .github/workflows/firestore.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/firestore-nightly.yml b/.github/workflows/firestore-nightly.yml index dca4a833f19..36beeadb4ac 100644 --- a/.github/workflows/firestore-nightly.yml +++ b/.github/workflows/firestore-nightly.yml @@ -71,7 +71,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.7' + python-version: '3.11' - name: Install Secret GoogleService-Info.plist run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/firestore-nightly.plist.gpg \ diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 065d0b365cc..7fe7842e00e 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -311,7 +311,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.7' + python-version: '3.11' - name: Setup build run: scripts/install_prereqs.sh Firestore ${{ runner.os }} cmake From 3451e130a3e20579300330d90801afa577930ff0 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jan 2025 01:17:58 -0500 Subject: [PATCH 05/84] firestore.yml: add workflow_dispatch trigger --- .github/workflows/firestore.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 7fe7842e00e..90c61962c6a 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -15,6 +15,7 @@ name: firestore on: + workflow_dispatch: pull_request: schedule: # Run every day at 12am (PST) - cron uses UTC times From ab5bc6941afdeb88dd2d92a9366334f2a81ebd0a Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Sat, 4 Jan 2025 23:21:08 -0500 Subject: [PATCH 06/84] thread_safe_memoizer.h: add missing #include --- Firestore/core/src/util/thread_safe_memoizer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 0ae090c00be..62fc9cb4efa 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ #define FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ +#include #include #include From 7d9f957ddc6acdae6f4dd987bf6807c190e69f52 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 19:07:03 +0000 Subject: [PATCH 07/84] string_format.h: create the StringifySink at the correct scope and pass it along so that its lifetime gets extended to the full-expression that uses FormatArg, as would have happened if `AlphaNum` was used as intended (i.e. as an argument to StrCat and StrAppend). --- Firestore/core/src/util/string_format.h | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Firestore/core/src/util/string_format.h b/Firestore/core/src/util/string_format.h index 2322dba0138..88ee07d51a8 100644 --- a/Firestore/core/src/util/string_format.h +++ b/Firestore/core/src/util/string_format.h @@ -65,8 +65,8 @@ struct FormatChoice<5> {}; class FormatArg : public absl::AlphaNum { public: template - FormatArg(T&& value) // NOLINT(runtime/explicit) - : FormatArg{std::forward(value), internal::FormatChoice<0>{}} { + FormatArg(T&& value, absl::strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit) + : FormatArg{std::forward(value), std::move(sink), internal::FormatChoice<0>{}} { } private: @@ -79,7 +79,7 @@ class FormatArg : public absl::AlphaNum { */ template {}>::type> - FormatArg(T bool_value, internal::FormatChoice<0>) + FormatArg(T bool_value, absl::strings_internal::StringifySink&&, internal::FormatChoice<0>) : AlphaNum(bool_value ? "true" : "false") { } @@ -90,7 +90,7 @@ class FormatArg : public absl::AlphaNum { template < typename T, typename = typename std::enable_if{}>::type> - FormatArg(T object, internal::FormatChoice<1>) + FormatArg(T object, absl::strings_internal::StringifySink&&, internal::FormatChoice<1>) : AlphaNum(MakeStringView([object description])) { } @@ -98,7 +98,7 @@ class FormatArg : public absl::AlphaNum { * Creates a FormatArg from any Objective-C Class type. Objective-C Class * types are a special struct that aren't of a type derived from NSObject. */ - FormatArg(Class object, internal::FormatChoice<1>) + FormatArg(Class object, absl::strings_internal::StringifySink&&, internal::FormatChoice<1>) : AlphaNum(MakeStringView(NSStringFromClass(object))) { } #endif @@ -108,7 +108,7 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(std::nullptr_t, internal::FormatChoice<2>) : AlphaNum("null") { + FormatArg(std::nullptr_t, absl::strings_internal::StringifySink&&, internal::FormatChoice<2>) : AlphaNum("null") { } /** @@ -116,7 +116,7 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(const char* string_value, internal::FormatChoice<3>) + FormatArg(const char* string_value, absl::strings_internal::StringifySink&&, internal::FormatChoice<3>) : AlphaNum(string_value == nullptr ? "null" : string_value) { } @@ -125,8 +125,8 @@ class FormatArg : public absl::AlphaNum { * hexadecimal integer literal. */ template - FormatArg(T* pointer_value, internal::FormatChoice<4>) - : AlphaNum(absl::Hex(reinterpret_cast(pointer_value))) { + FormatArg(T* pointer_value, absl::strings_internal::StringifySink&& sink, internal::FormatChoice<4>) + : AlphaNum(absl::Hex(reinterpret_cast(pointer_value)), std::move(sink)) { } /** @@ -134,7 +134,7 @@ class FormatArg : public absl::AlphaNum { * absl::AlphaNum accepts. */ template - FormatArg(T&& value, internal::FormatChoice<5>) + FormatArg(T&& value, absl::strings_internal::StringifySink&&, internal::FormatChoice<5>) : AlphaNum(std::forward(value)) { } }; @@ -157,8 +157,7 @@ class FormatArg : public absl::AlphaNum { */ template std::string StringFormat(const char* format, const FA&... args) { - return internal::StringFormatPieces( - format, {static_cast(args).Piece()...}); + return internal::StringFormatPieces(format, {static_cast(args).Piece()...}); } inline std::string StringFormat() { From 1f00fb11014ef7a13bd81226db912b4c4fa8b28a Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 14:16:06 -0500 Subject: [PATCH 08/84] string_format.h: format code --- Firestore/core/src/util/string_format.h | 42 ++++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/Firestore/core/src/util/string_format.h b/Firestore/core/src/util/string_format.h index 88ee07d51a8..92676c3c7c8 100644 --- a/Firestore/core/src/util/string_format.h +++ b/Firestore/core/src/util/string_format.h @@ -65,8 +65,11 @@ struct FormatChoice<5> {}; class FormatArg : public absl::AlphaNum { public: template - FormatArg(T&& value, absl::strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit) - : FormatArg{std::forward(value), std::move(sink), internal::FormatChoice<0>{}} { + FormatArg(T&& value, + absl::strings_internal::StringifySink&& sink = + {}) // NOLINT(runtime/explicit) + : FormatArg{std::forward(value), std::move(sink), + internal::FormatChoice<0>{}} { } private: @@ -79,7 +82,9 @@ class FormatArg : public absl::AlphaNum { */ template {}>::type> - FormatArg(T bool_value, absl::strings_internal::StringifySink&&, internal::FormatChoice<0>) + FormatArg(T bool_value, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<0>) : AlphaNum(bool_value ? "true" : "false") { } @@ -90,7 +95,9 @@ class FormatArg : public absl::AlphaNum { template < typename T, typename = typename std::enable_if{}>::type> - FormatArg(T object, absl::strings_internal::StringifySink&&, internal::FormatChoice<1>) + FormatArg(T object, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<1>) : AlphaNum(MakeStringView([object description])) { } @@ -98,7 +105,9 @@ class FormatArg : public absl::AlphaNum { * Creates a FormatArg from any Objective-C Class type. Objective-C Class * types are a special struct that aren't of a type derived from NSObject. */ - FormatArg(Class object, absl::strings_internal::StringifySink&&, internal::FormatChoice<1>) + FormatArg(Class object, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<1>) : AlphaNum(MakeStringView(NSStringFromClass(object))) { } #endif @@ -108,7 +117,10 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(std::nullptr_t, absl::strings_internal::StringifySink&&, internal::FormatChoice<2>) : AlphaNum("null") { + FormatArg(std::nullptr_t, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<2>) + : AlphaNum("null") { } /** @@ -116,7 +128,9 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(const char* string_value, absl::strings_internal::StringifySink&&, internal::FormatChoice<3>) + FormatArg(const char* string_value, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<3>) : AlphaNum(string_value == nullptr ? "null" : string_value) { } @@ -125,8 +139,11 @@ class FormatArg : public absl::AlphaNum { * hexadecimal integer literal. */ template - FormatArg(T* pointer_value, absl::strings_internal::StringifySink&& sink, internal::FormatChoice<4>) - : AlphaNum(absl::Hex(reinterpret_cast(pointer_value)), std::move(sink)) { + FormatArg(T* pointer_value, + absl::strings_internal::StringifySink&& sink, + internal::FormatChoice<4>) + : AlphaNum(absl::Hex(reinterpret_cast(pointer_value)), + std::move(sink)) { } /** @@ -134,7 +151,9 @@ class FormatArg : public absl::AlphaNum { * absl::AlphaNum accepts. */ template - FormatArg(T&& value, absl::strings_internal::StringifySink&&, internal::FormatChoice<5>) + FormatArg(T&& value, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<5>) : AlphaNum(std::forward(value)) { } }; @@ -157,7 +176,8 @@ class FormatArg : public absl::AlphaNum { */ template std::string StringFormat(const char* format, const FA&... args) { - return internal::StringFormatPieces(format, {static_cast(args).Piece()...}); + return internal::StringFormatPieces( + format, {static_cast(args).Piece()...}); } inline std::string StringFormat() { From aeb0b05242de29eec76789d9079599f805a36b42 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 14:19:41 -0500 Subject: [PATCH 09/84] string_format.h: fix use-after-free bug due to insufficient lifetime extension of the StringifySink object --- Firestore/core/src/util/string_format.h | 39 ++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/Firestore/core/src/util/string_format.h b/Firestore/core/src/util/string_format.h index 2322dba0138..92676c3c7c8 100644 --- a/Firestore/core/src/util/string_format.h +++ b/Firestore/core/src/util/string_format.h @@ -65,8 +65,11 @@ struct FormatChoice<5> {}; class FormatArg : public absl::AlphaNum { public: template - FormatArg(T&& value) // NOLINT(runtime/explicit) - : FormatArg{std::forward(value), internal::FormatChoice<0>{}} { + FormatArg(T&& value, + absl::strings_internal::StringifySink&& sink = + {}) // NOLINT(runtime/explicit) + : FormatArg{std::forward(value), std::move(sink), + internal::FormatChoice<0>{}} { } private: @@ -79,7 +82,9 @@ class FormatArg : public absl::AlphaNum { */ template {}>::type> - FormatArg(T bool_value, internal::FormatChoice<0>) + FormatArg(T bool_value, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<0>) : AlphaNum(bool_value ? "true" : "false") { } @@ -90,7 +95,9 @@ class FormatArg : public absl::AlphaNum { template < typename T, typename = typename std::enable_if{}>::type> - FormatArg(T object, internal::FormatChoice<1>) + FormatArg(T object, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<1>) : AlphaNum(MakeStringView([object description])) { } @@ -98,7 +105,9 @@ class FormatArg : public absl::AlphaNum { * Creates a FormatArg from any Objective-C Class type. Objective-C Class * types are a special struct that aren't of a type derived from NSObject. */ - FormatArg(Class object, internal::FormatChoice<1>) + FormatArg(Class object, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<1>) : AlphaNum(MakeStringView(NSStringFromClass(object))) { } #endif @@ -108,7 +117,10 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(std::nullptr_t, internal::FormatChoice<2>) : AlphaNum("null") { + FormatArg(std::nullptr_t, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<2>) + : AlphaNum("null") { } /** @@ -116,7 +128,9 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(const char* string_value, internal::FormatChoice<3>) + FormatArg(const char* string_value, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<3>) : AlphaNum(string_value == nullptr ? "null" : string_value) { } @@ -125,8 +139,11 @@ class FormatArg : public absl::AlphaNum { * hexadecimal integer literal. */ template - FormatArg(T* pointer_value, internal::FormatChoice<4>) - : AlphaNum(absl::Hex(reinterpret_cast(pointer_value))) { + FormatArg(T* pointer_value, + absl::strings_internal::StringifySink&& sink, + internal::FormatChoice<4>) + : AlphaNum(absl::Hex(reinterpret_cast(pointer_value)), + std::move(sink)) { } /** @@ -134,7 +151,9 @@ class FormatArg : public absl::AlphaNum { * absl::AlphaNum accepts. */ template - FormatArg(T&& value, internal::FormatChoice<5>) + FormatArg(T&& value, + absl::strings_internal::StringifySink&&, + internal::FormatChoice<5>) : AlphaNum(std::forward(value)) { } }; From 82e5289b97ba16b386644fb71d1865f2cbc7882d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 20:03:27 +0000 Subject: [PATCH 10/84] string_format_test.cc: strengthen Pointer test to match a regex rather than just ASSERT_NE --- .../core/test/unit/util/string_format_test.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Firestore/core/test/unit/util/string_format_test.cc b/Firestore/core/test/unit/util/string_format_test.cc index b9236017a98..1d7b05585b8 100644 --- a/Firestore/core/test/unit/util/string_format_test.cc +++ b/Firestore/core/test/unit/util/string_format_test.cc @@ -17,6 +17,7 @@ #include "Firestore/core/src/util/string_format.h" #include "absl/strings/string_view.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { @@ -72,14 +73,18 @@ TEST(StringFormatTest, Bool) { EXPECT_EQ("Hello false", StringFormat("Hello %s", false)); } -TEST(StringFormatTest, Pointer) { - // pointers implicitly convert to bool. Make sure this doesn't happen in - // this API. - int value = 4; - EXPECT_NE("Hello true", StringFormat("Hello %s", &value)); +TEST(StringFormatTest, NullPointer) { + // pointers implicitly convert to bool. Make sure this doesn't happen here. EXPECT_EQ("Hello null", StringFormat("Hello %s", nullptr)); } +TEST(StringFormatTest, NonNullPointer) { + // pointers implicitly convert to bool. Make sure this doesn't happen here. + int value = 4; + EXPECT_THAT(StringFormat("Hello %s", &value), + testing::MatchesRegex("Hello (0x)?[0123456789abcdefABCDEF]+")); +} + TEST(StringFormatTest, Mixed) { EXPECT_EQ("string=World, bool=true, int=42, float=1.5", StringFormat("string=%s, bool=%s, int=%s, float=%s", "World", true, From 24066f570fbc54081fd1b1486bc9fc721687f151 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 20:06:08 +0000 Subject: [PATCH 11/84] Firestore/CHANGELOG.md entry added --- Firestore/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index ee7ea12f87f..ad57793f111 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Fixed use-after-free bug when internally formatting strings. (#14306) + # 11.6.0 - [fixed] Add conditional `Sendable` conformance so `ServerTimestamp` is `Sendable` if `T` is `Sendable`. (#14042) From 2a30d70840c3453b68bcb874cb5dcb85cd391c69 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 21:27:33 +0000 Subject: [PATCH 12/84] use std::shared_ptr to much more easily implement the shared ownership semantics --- .../core/src/util/thread_safe_memoizer.h | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 62fc9cb4efa..51982b00aae 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -34,49 +34,54 @@ namespace util { template class ThreadSafeMemoizer { public: - ThreadSafeMemoizer() = default; - - ThreadSafeMemoizer(const ThreadSafeMemoizer&) { - } - - ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer&) { - return *this; + ThreadSafeMemoizer() + : memoized_(new std::atomic(nullptr), MemoizedValueDeleter) { } + ThreadSafeMemoizer(const ThreadSafeMemoizer& other) = default; + ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) = default; ThreadSafeMemoizer(ThreadSafeMemoizer&& other) = default; ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) = default; - ~ThreadSafeMemoizer() { - delete memoized_.load(); - } - /** * Memoize a value. * - * If there is no memoized value then the given function is called to create - * the value, returning a reference to the created value and storing the - * pointer to the created value. On the other hand, if there _is_ a memoized - * value from a previous invocation then a reference to that object is - * returned and the given function is not called. Note that the given function - * may be called more than once and, therefore, must be idempotent. + * If there is _no_ memoized value then the given function is called to create + * the object to memoize. The created object is then stored for use in future + * invocations as the "memoized value". Finally, a reference to the created + * object is returned. + * + * On the other hand, if there _is_ a memoized value, then a reference to that + * memoized value object is returned and the given function is _not_ called. + * + * The given function *must* be idempotent because it _may_ be called more + * than once due to the semantics of std::atomic::compare_exchange_weak(). + * + * No reference to the given function is retained by this object, and the + * function be called synchronously, if it is called at all. */ const T& memoize(std::function()> func) { while (true) { - T* old_memoized = memoized_.load(); + T* old_memoized = memoized_->load(); if (old_memoized) { return *old_memoized; } std::unique_ptr new_memoized = func(); - if (memoized_.compare_exchange_strong(old_memoized, new_memoized.get())) { + if (memoized_->compare_exchange_weak(old_memoized, new_memoized.get())) { return *new_memoized.release(); } } } private: - std::atomic memoized_ = {nullptr}; + std::shared_ptr> memoized_; + + static void MemoizedValueDeleter(std::atomic* value) { + delete value->load(); + delete value; + } }; } // namespace util From f5ae906f2a61446926a3d7fa41a7073df24aef2e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 6 Jan 2025 21:34:14 +0000 Subject: [PATCH 13/84] remove the comment about making thread_safe_memoizer.h copyable --- Firestore/core/src/util/thread_safe_memoizer.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 51982b00aae..5cb36db8aee 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -28,8 +28,6 @@ namespace util { /** * Stores a memoized value in a manner that is safe to be shared between * multiple threads. - * - * TODO(b/299933587) Make `ThreadSafeMemoizer` copyable and moveable. */ template class ThreadSafeMemoizer { From d925a22ce45e5d91ede05eb6db8ed484d96e9534 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 18:07:25 +0000 Subject: [PATCH 14/84] thread_safe_memoizer.h reword --- Firestore/core/src/core/composite_filter.cc | 17 ++-- Firestore/core/src/core/composite_filter.h | 2 +- Firestore/core/src/core/field_filter.cc | 10 +-- Firestore/core/src/core/field_filter.h | 4 +- Firestore/core/src/core/filter.h | 16 ++-- Firestore/core/src/core/query.cc | 80 +++++++++---------- Firestore/core/src/core/query.h | 21 +++-- .../core/src/util/thread_safe_memoizer.h | 42 ++++++---- .../unit/util/thread_safe_memoizer_test.cc | 27 +++---- 9 files changed, 111 insertions(+), 108 deletions(-) diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 410945277f9..e54bd748b0f 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -142,16 +142,13 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( return nullptr; } -const std::vector& CompositeFilter::Rep::GetFlattenedFilters() - const { - return memoized_flattened_filters_.memoize([&]() { - auto flattened_filters = absl::make_unique>(); - for (const auto& filter : filters()) - std::copy(filter.GetFlattenedFilters().begin(), - filter.GetFlattenedFilters().end(), - std::back_inserter(*flattened_filters)); - return flattened_filters; - }); +std::shared_ptr> CompositeFilter::Rep::CalculateFlattenedFilters() const { + auto flattened_filters = absl::make_unique>(); + for (const auto& filter : filters()) + std::copy(filter.GetFlattenedFilters().begin(), + filter.GetFlattenedFilters().end(), + std::back_inserter(*flattened_filters)); + return flattened_filters; } } // namespace core diff --git a/Firestore/core/src/core/composite_filter.h b/Firestore/core/src/core/composite_filter.h index 24671d3e44e..3271397411a 100644 --- a/Firestore/core/src/core/composite_filter.h +++ b/Firestore/core/src/core/composite_filter.h @@ -138,7 +138,7 @@ class CompositeFilter : public Filter { return filters_.empty(); } - const std::vector& GetFlattenedFilters() const override; + std::shared_ptr> CalculateFlattenedFilters() const override; std::vector GetFilters() const override { return filters(); diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index 3a034f05d36..f7be2f195f1 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -123,13 +123,11 @@ FieldFilter::FieldFilter(std::shared_ptr rep) : Filter(std::move(rep)) { } -const std::vector& FieldFilter::Rep::GetFlattenedFilters() const { +std::shared_ptr> FieldFilter::Rep::CalculateFlattenedFilters() const { // This is already a field filter, so we return a vector of size one. - return memoized_flattened_filters_.memoize([&]() { - auto filters = absl::make_unique>(); - filters->push_back(FieldFilter(std::make_shared(*this))); - return filters; - }); + auto filters = std::make_shared>(); + filters->push_back(FieldFilter(std::make_shared(*this))); + return filters; } std::vector FieldFilter::Rep::GetFilters() const { diff --git a/Firestore/core/src/core/field_filter.h b/Firestore/core/src/core/field_filter.h index 48219f222f2..974b382ccb9 100644 --- a/Firestore/core/src/core/field_filter.h +++ b/Firestore/core/src/core/field_filter.h @@ -117,8 +117,6 @@ class FieldFilter : public Filter { return false; } - const std::vector& GetFlattenedFilters() const override; - std::vector GetFilters() const override; protected: @@ -140,6 +138,8 @@ class FieldFilter : public Filter { bool MatchesComparison(util::ComparisonResult comparison) const; + std::shared_ptr> CalculateFlattenedFilters() const override; + private: friend class FieldFilter; diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index 17942439fe3..33a615b0ca1 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -145,21 +145,21 @@ class Filter { virtual bool IsEmpty() const = 0; - virtual const std::vector& GetFlattenedFilters() const = 0; + virtual const std::vector& GetFlattenedFilters() const { + return flattened_filters_.value(); + } virtual std::vector GetFilters() const = 0; + protected: + virtual std::shared_ptr> CalculateFlattenedFilters() const = 0; + + private: /** * Memoized list of all field filters that can be found by * traversing the tree of filters contained in this composite filter. - * - * Use a `std::shared_ptr` rather than using - * `ThreadSafeMemoizer` directly so that this class is copyable - * (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag` - * member variable, which is not copyable). */ - mutable util::ThreadSafeMemoizer> - memoized_flattened_filters_; + mutable util::ThreadSafeMemoizer> flattened_filters_{[&] { return CalculateFlattenedFilters(); }}; }; explicit Filter(std::shared_ptr&& rep) : rep_(rep) { diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 96f997da96f..519cd6536b7 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -92,44 +92,42 @@ absl::optional Query::FindOpInsideFilters( return absl::nullopt; } -const std::vector& Query::normalized_order_bys() const { - return memoized_normalized_order_bys_.memoize([&]() { - // Any explicit order by fields should be added as is. - auto result = absl::make_unique>(explicit_order_bys_); - std::set fieldsNormalized; - for (const OrderBy& order_by : explicit_order_bys_) { - fieldsNormalized.insert(order_by.field()); - } +std::shared_ptr> Query::calculate_normalized_order_bys() const { + auto result = std::make_shared>(explicit_order_bys_); - // The order of the implicit ordering always matches the last explicit order - // by. - Direction last_direction = explicit_order_bys_.empty() - ? Direction::Ascending - : explicit_order_bys_.back().direction(); - - // Any inequality fields not explicitly ordered should be implicitly ordered - // in a lexicographical order. When there are multiple inequality filters on - // the same field, the field should be added only once. Note: - // `std::set` sorts the key field before other fields. - // However, we want the key field to be sorted last. - const std::set inequality_fields = - InequalityFilterFields(); - - for (const model::FieldPath& field : inequality_fields) { - if (fieldsNormalized.find(field) == fieldsNormalized.end() && - !field.IsKeyFieldPath()) { - result->push_back(OrderBy(field, last_direction)); - } - } + std::set fieldsNormalized; + for (const OrderBy& order_by : explicit_order_bys_) { + fieldsNormalized.insert(order_by.field()); + } - // Add the document key field to the last if it is not explicitly ordered. - if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == - fieldsNormalized.end()) { - result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); - } + // The order of the implicit ordering always matches the last explicit order + // by. + Direction last_direction = explicit_order_bys_.empty() + ? Direction::Ascending + : explicit_order_bys_.back().direction(); + + // Any inequality fields not explicitly ordered should be implicitly ordered + // in a lexicographical order. When there are multiple inequality filters on + // the same field, the field should be added only once. Note: + // `std::set` sorts the key field before other fields. + // However, we want the key field to be sorted last. + const std::set inequality_fields = + InequalityFilterFields(); + + for (const model::FieldPath& field : inequality_fields) { + if (fieldsNormalized.find(field) == fieldsNormalized.end() && + !field.IsKeyFieldPath()) { + result->push_back(OrderBy(field, last_direction)); + } + } - return result; - }); + // Add the document key field to the last if it is not explicitly ordered. + if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == + fieldsNormalized.end()) { + result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); + } + + return result; } LimitType Query::limit_type() const { @@ -297,16 +295,12 @@ std::string Query::ToString() const { return absl::StrCat("Query(canonical_id=", CanonicalId(), ")"); } -const Target& Query::ToTarget() const& { - return memoized_target_.memoize([&]() { - return absl::make_unique(ToTarget(normalized_order_bys())); - }); +std::shared_ptr Query::calculate_target() const { + return std::make_shared(ToTarget(normalized_order_bys())); } -const Target& Query::ToAggregateTarget() const& { - return memoized_aggregate_target_.memoize([&]() { - return absl::make_unique(ToTarget(explicit_order_bys_)); - }); +std::shared_ptr Query::calculate_aggregate_target() const { + return std::make_shared(ToTarget(explicit_order_bys_)); } Target Query::ToTarget(const std::vector& order_bys) const { diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index 9f73cc5e120..533878ccfde 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -148,7 +148,9 @@ class Query { * This might include additional sort orders added implicitly to match the * backend behavior. */ - const std::vector& normalized_order_bys() const; + const std::vector& normalized_order_bys() const { + return normalized_order_bys_.value(); + } bool has_limit() const { return limit_ != Target::kNoLimit; @@ -246,7 +248,9 @@ class Query { * Returns a `Target` instance this query will be mapped to in backend * and local store. */ - const Target& ToTarget() const&; + const Target& ToTarget() const& { + return target_.value(); + } /** * Returns a `Target` instance this query will be mapped to in backend @@ -254,7 +258,9 @@ class Query { * for non-aggregate queries, aggregate query targets do not contain * normalized order-bys, they only contain explicit order-bys. */ - const Target& ToAggregateTarget() const&; + const Target& ToAggregateTarget() const& { + return aggregate_target_.value(); + } friend std::ostream& operator<<(std::ostream& os, const Query& query); @@ -295,16 +301,19 @@ class Query { // member variable, which is not copyable). // The memoized list of sort orders. + std::shared_ptr> calculate_normalized_order_bys() const; mutable util::ThreadSafeMemoizer> - memoized_normalized_order_bys_; + normalized_order_bys_{[&] {return calculate_normalized_order_bys(); }}; // The corresponding Target of this Query instance. - mutable util::ThreadSafeMemoizer memoized_target_; + std::shared_ptr calculate_target() const; + mutable util::ThreadSafeMemoizer target_{[&] {return calculate_target(); }}; // The corresponding aggregate Target of this Query instance. Unlike targets // for non-aggregate queries, aggregate query targets do not contain // normalized order-bys, they only contain explicit order-bys. - mutable util::ThreadSafeMemoizer memoized_aggregate_target_; + std::shared_ptr calculate_aggregate_target() const; + mutable util::ThreadSafeMemoizer aggregate_target_{[&] {return calculate_aggregate_target(); }};; }; bool operator==(const Query& lhs, const Query& rhs); diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 5cb36db8aee..1fa2439f83f 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace firebase { namespace firestore { @@ -32,14 +33,25 @@ namespace util { template class ThreadSafeMemoizer { public: - ThreadSafeMemoizer() - : memoized_(new std::atomic(nullptr), MemoizedValueDeleter) { + explicit ThreadSafeMemoizer(std::function()> func) : func_(std::move(func)) { } - ThreadSafeMemoizer(const ThreadSafeMemoizer& other) = default; - ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) = default; - ThreadSafeMemoizer(ThreadSafeMemoizer&& other) = default; - ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) = default; + ThreadSafeMemoizer(const ThreadSafeMemoizer& other) : func_(other.func_), memoized_(std::atomic_load(&other.memoized_)) {} + + ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { + func_ = other.func_; + std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); + return *this; + } + + ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept : func_(std::move(other.func_)), memoized_(std::atomic_load(&other.memoized_)) { + } + + ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { + func_ = std::move(other.func_); + std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); + return *this; + } /** * Memoize a value. @@ -58,28 +70,24 @@ class ThreadSafeMemoizer { * No reference to the given function is retained by this object, and the * function be called synchronously, if it is called at all. */ - const T& memoize(std::function()> func) { + const T& value() { + std::shared_ptr old_memoized = std::atomic_load(&memoized_); while (true) { - T* old_memoized = memoized_->load(); if (old_memoized) { return *old_memoized; } - std::unique_ptr new_memoized = func(); + std::shared_ptr new_memoized = func_(); - if (memoized_->compare_exchange_weak(old_memoized, new_memoized.get())) { - return *new_memoized.release(); + if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, new_memoized)) { + return *new_memoized; } } } private: - std::shared_ptr> memoized_; - - static void MemoizedValueDeleter(std::atomic* value) { - delete value->load(); - delete value; - } + std::function()> func_; + std::shared_ptr memoized_; }; } // namespace util diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 9a634c215ba..f4e95c1e25c 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -20,34 +20,33 @@ #include "absl/memory/memory.h" #include "gtest/gtest.h" -namespace firebase { -namespace firestore { -namespace util { +namespace { + +using firebase::firestore::util::ThreadSafeMemoizer; TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { std::atomic global_int{77}; - auto expensive_lambda = [&]() { + auto expensive_lambda = [&] { // Simulate an expensive operation std::this_thread::sleep_for(std::chrono::milliseconds(100)); // If the lambda gets executed multiple times, threads will see incremented // `global_int`. - global_int++; - return absl::make_unique(global_int.load()); + ++global_int; + return std::make_shared(global_int.load()); }; - const int num_threads = 5; - const int expected_result = 78; + constexpr int num_threads = 5; + constexpr int expected_result = 78; // Create a thread safe memoizer and multiple threads. - util::ThreadSafeMemoizer memoized_result; + ThreadSafeMemoizer memoized_result(expensive_lambda); std::vector threads; for (int i = 0; i < num_threads; ++i) { threads.emplace_back( - [&memoized_result, expected_result, &expensive_lambda]() { - const int& actual_result = memoized_result.memoize(expensive_lambda); - + [&memoized_result, expected_result] { + const int& actual_result = memoized_result.value(); // Verify that all threads get the same memoized result. EXPECT_EQ(actual_result, expected_result); }); @@ -58,6 +57,4 @@ TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { } } -} // namespace util -} // namespace firestore -} // namespace firebase +} // namespace \ No newline at end of file From 903df59b34864fa8d2859ccd2be04a00be621d9a Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 18:20:19 +0000 Subject: [PATCH 15/84] get things to compile --- Firestore/core/src/core/filter.h | 6 +++-- Firestore/core/src/core/query.cc | 7 +++--- Firestore/core/src/core/query.h | 22 ++++++++++--------- .../core/src/util/thread_safe_memoizer.h | 15 +++++-------- .../unit/util/thread_safe_memoizer_test.cc | 6 ++--- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index 33a615b0ca1..4a3a90a8cd2 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_CORE_FILTER_H_ #define FIRESTORE_CORE_SRC_CORE_FILTER_H_ +#include #include #include #include @@ -146,7 +147,8 @@ class Filter { virtual bool IsEmpty() const = 0; virtual const std::vector& GetFlattenedFilters() const { - return flattened_filters_.value(); + const auto func = std::bind(&Rep::CalculateFlattenedFilters, this); + return memoized_flattened_filters_.value(func); } virtual std::vector GetFilters() const = 0; @@ -159,7 +161,7 @@ class Filter { * Memoized list of all field filters that can be found by * traversing the tree of filters contained in this composite filter. */ - mutable util::ThreadSafeMemoizer> flattened_filters_{[&] { return CalculateFlattenedFilters(); }}; + mutable util::ThreadSafeMemoizer> memoized_flattened_filters_; }; explicit Filter(std::shared_ptr&& rep) : rep_(rep) { diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 519cd6536b7..c2fd9f3e322 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -17,6 +17,7 @@ #include "Firestore/core/src/core/query.h" #include +#include #include #include "Firestore/core/src/core/bound.h" @@ -92,7 +93,7 @@ absl::optional Query::FindOpInsideFilters( return absl::nullopt; } -std::shared_ptr> Query::calculate_normalized_order_bys() const { +std::shared_ptr> Query::CalculateNormalizedOrderBys() const { auto result = std::make_shared>(explicit_order_bys_); std::set fieldsNormalized; @@ -295,11 +296,11 @@ std::string Query::ToString() const { return absl::StrCat("Query(canonical_id=", CanonicalId(), ")"); } -std::shared_ptr Query::calculate_target() const { +std::shared_ptr Query::CalculateTarget() const { return std::make_shared(ToTarget(normalized_order_bys())); } -std::shared_ptr Query::calculate_aggregate_target() const { +std::shared_ptr Query::CalculateAggregateTarget() const { return std::make_shared(ToTarget(explicit_order_bys_)); } diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index 533878ccfde..d8993e7a0a6 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -149,7 +149,8 @@ class Query { * backend behavior. */ const std::vector& normalized_order_bys() const { - return normalized_order_bys_.value(); + const auto func = std::bind(&Query::CalculateNormalizedOrderBys, this); + return memoized_normalized_order_bys_.value(func); } bool has_limit() const { @@ -249,7 +250,8 @@ class Query { * and local store. */ const Target& ToTarget() const& { - return target_.value(); + const auto func = std::bind(&Query::CalculateTarget, this); + return memoized_target_.value(func); } /** @@ -259,7 +261,8 @@ class Query { * normalized order-bys, they only contain explicit order-bys. */ const Target& ToAggregateTarget() const& { - return aggregate_target_.value(); + const auto func = std::bind(&Query::CalculateAggregateTarget, this); + return memoized_aggregate_target_.value(func); } friend std::ostream& operator<<(std::ostream& os, const Query& query); @@ -301,19 +304,18 @@ class Query { // member variable, which is not copyable). // The memoized list of sort orders. - std::shared_ptr> calculate_normalized_order_bys() const; - mutable util::ThreadSafeMemoizer> - normalized_order_bys_{[&] {return calculate_normalized_order_bys(); }}; + std::shared_ptr> CalculateNormalizedOrderBys() const; + mutable util::ThreadSafeMemoizer> memoized_normalized_order_bys_; // The corresponding Target of this Query instance. - std::shared_ptr calculate_target() const; - mutable util::ThreadSafeMemoizer target_{[&] {return calculate_target(); }}; + std::shared_ptr CalculateTarget() const; + mutable util::ThreadSafeMemoizer memoized_target_; // The corresponding aggregate Target of this Query instance. Unlike targets // for non-aggregate queries, aggregate query targets do not contain // normalized order-bys, they only contain explicit order-bys. - std::shared_ptr calculate_aggregate_target() const; - mutable util::ThreadSafeMemoizer aggregate_target_{[&] {return calculate_aggregate_target(); }};; + std::shared_ptr CalculateAggregateTarget() const; + mutable util::ThreadSafeMemoizer memoized_aggregate_target_; }; bool operator==(const Query& lhs, const Query& rhs); diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 1fa2439f83f..470289ebe5b 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -20,7 +20,6 @@ #include #include #include -#include namespace firebase { namespace firestore { @@ -33,22 +32,19 @@ namespace util { template class ThreadSafeMemoizer { public: - explicit ThreadSafeMemoizer(std::function()> func) : func_(std::move(func)) { - } + ThreadSafeMemoizer() = default; - ThreadSafeMemoizer(const ThreadSafeMemoizer& other) : func_(other.func_), memoized_(std::atomic_load(&other.memoized_)) {} + ThreadSafeMemoizer(const ThreadSafeMemoizer& other) : memoized_(std::atomic_load(&other.memoized_)) {} ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { - func_ = other.func_; std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); return *this; } - ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept : func_(std::move(other.func_)), memoized_(std::atomic_load(&other.memoized_)) { + ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept : memoized_(std::atomic_load(&other.memoized_)) { } ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { - func_ = std::move(other.func_); std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); return *this; } @@ -70,14 +66,14 @@ class ThreadSafeMemoizer { * No reference to the given function is retained by this object, and the * function be called synchronously, if it is called at all. */ - const T& value() { + const T& value(const std::function()>& func) { std::shared_ptr old_memoized = std::atomic_load(&memoized_); while (true) { if (old_memoized) { return *old_memoized; } - std::shared_ptr new_memoized = func_(); + std::shared_ptr new_memoized = func(); if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, new_memoized)) { return *new_memoized; @@ -86,7 +82,6 @@ class ThreadSafeMemoizer { } private: - std::function()> func_; std::shared_ptr memoized_; }; diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index f4e95c1e25c..a4b359d25ed 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -40,13 +40,13 @@ TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { constexpr int expected_result = 78; // Create a thread safe memoizer and multiple threads. - ThreadSafeMemoizer memoized_result(expensive_lambda); + ThreadSafeMemoizer memoized_result; std::vector threads; for (int i = 0; i < num_threads; ++i) { threads.emplace_back( - [&memoized_result, expected_result] { - const int& actual_result = memoized_result.value(); + [&memoized_result, expected_result, &expensive_lambda] { + const int& actual_result = memoized_result.value(expensive_lambda); // Verify that all threads get the same memoized result. EXPECT_EQ(actual_result, expected_result); }); From d21fb1713f0d1a9de1e6c033769e9bf48b63cc24 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 18:48:42 +0000 Subject: [PATCH 16/84] minor code cleanup --- Firestore/core/src/core/composite_filter.cc | 4 ++-- Firestore/core/src/core/field_filter.cc | 2 +- Firestore/core/src/core/filter.h | 2 ++ Firestore/core/src/core/query.cc | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index e54bd748b0f..759eaa6536c 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -17,13 +17,13 @@ #include "Firestore/core/src/core/composite_filter.h" #include +#include #include #include "Firestore/core/src/core/field_filter.h" #include "Firestore/core/src/model/field_path.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/string_format.h" -#include "absl/memory/memory.h" #include "absl/strings/str_join.h" namespace firebase { @@ -143,7 +143,7 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( } std::shared_ptr> CompositeFilter::Rep::CalculateFlattenedFilters() const { - auto flattened_filters = absl::make_unique>(); + auto flattened_filters = std::make_shared>(); for (const auto& filter : filters()) std::copy(filter.GetFlattenedFilters().begin(), filter.GetFlattenedFilters().end(), diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index f7be2f195f1..2545855966c 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -16,6 +16,7 @@ #include "Firestore/core/src/core/field_filter.h" +#include #include #include "Firestore/core/src/core/array_contains_any_filter.h" @@ -32,7 +33,6 @@ #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" #include "absl/algorithm/container.h" -#include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/types/optional.h" diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index 4a3a90a8cd2..b94bd4f5a98 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -115,6 +115,8 @@ class Filter { protected: class Rep { public: + Rep() = default; + virtual ~Rep() = default; virtual Type type() const { diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index c2fd9f3e322..918ab50847b 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -31,7 +31,6 @@ #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" #include "absl/algorithm/container.h" -#include "absl/memory/memory.h" #include "absl/strings/str_cat.h" namespace firebase { From 2ca4d15d758213591abbfc8fa68fbb72fd5827c7 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 18:54:53 +0000 Subject: [PATCH 17/84] more cleanup; --- Firestore/core/src/core/composite_filter.cc | 3 ++- Firestore/core/src/core/composite_filter.h | 3 ++- Firestore/core/src/core/field_filter.cc | 3 ++- Firestore/core/src/core/field_filter.h | 3 ++- Firestore/core/src/core/filter.h | 6 ++++-- Firestore/core/src/core/query.cc | 12 ++++++------ Firestore/core/src/core/query.h | 3 ++- Firestore/core/src/util/thread_safe_memoizer.h | 10 +++++++--- 8 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 759eaa6536c..02186989330 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -142,7 +142,8 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( return nullptr; } -std::shared_ptr> CompositeFilter::Rep::CalculateFlattenedFilters() const { +std::shared_ptr> +CompositeFilter::Rep::CalculateFlattenedFilters() const { auto flattened_filters = std::make_shared>(); for (const auto& filter : filters()) std::copy(filter.GetFlattenedFilters().begin(), diff --git a/Firestore/core/src/core/composite_filter.h b/Firestore/core/src/core/composite_filter.h index 3271397411a..2858271a358 100644 --- a/Firestore/core/src/core/composite_filter.h +++ b/Firestore/core/src/core/composite_filter.h @@ -138,7 +138,8 @@ class CompositeFilter : public Filter { return filters_.empty(); } - std::shared_ptr> CalculateFlattenedFilters() const override; + std::shared_ptr> CalculateFlattenedFilters() + const override; std::vector GetFilters() const override { return filters(); diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index 2545855966c..2867e3ba8ba 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -123,7 +123,8 @@ FieldFilter::FieldFilter(std::shared_ptr rep) : Filter(std::move(rep)) { } -std::shared_ptr> FieldFilter::Rep::CalculateFlattenedFilters() const { +std::shared_ptr> +FieldFilter::Rep::CalculateFlattenedFilters() const { // This is already a field filter, so we return a vector of size one. auto filters = std::make_shared>(); filters->push_back(FieldFilter(std::make_shared(*this))); diff --git a/Firestore/core/src/core/field_filter.h b/Firestore/core/src/core/field_filter.h index 974b382ccb9..2f03254e1a1 100644 --- a/Firestore/core/src/core/field_filter.h +++ b/Firestore/core/src/core/field_filter.h @@ -138,7 +138,8 @@ class FieldFilter : public Filter { bool MatchesComparison(util::ComparisonResult comparison) const; - std::shared_ptr> CalculateFlattenedFilters() const override; + std::shared_ptr> CalculateFlattenedFilters() + const override; private: friend class FieldFilter; diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index b94bd4f5a98..bab79599cc6 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -156,14 +156,16 @@ class Filter { virtual std::vector GetFilters() const = 0; protected: - virtual std::shared_ptr> CalculateFlattenedFilters() const = 0; + virtual std::shared_ptr> + CalculateFlattenedFilters() const = 0; private: /** * Memoized list of all field filters that can be found by * traversing the tree of filters contained in this composite filter. */ - mutable util::ThreadSafeMemoizer> memoized_flattened_filters_; + mutable util::ThreadSafeMemoizer> + memoized_flattened_filters_; }; explicit Filter(std::shared_ptr&& rep) : rep_(rep) { diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 918ab50847b..1c8394748d9 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -92,9 +92,10 @@ absl::optional Query::FindOpInsideFilters( return absl::nullopt; } -std::shared_ptr> Query::CalculateNormalizedOrderBys() const { +std::shared_ptr> Query::CalculateNormalizedOrderBys() + const { + // Any explicit order by fields should be added as is. auto result = std::make_shared>(explicit_order_bys_); - std::set fieldsNormalized; for (const OrderBy& order_by : explicit_order_bys_) { fieldsNormalized.insert(order_by.field()); @@ -111,21 +112,20 @@ std::shared_ptr> Query::CalculateNormalizedOrderBys() const // the same field, the field should be added only once. Note: // `std::set` sorts the key field before other fields. // However, we want the key field to be sorted last. - const std::set inequality_fields = - InequalityFilterFields(); + const std::set inequality_fields = InequalityFilterFields(); for (const model::FieldPath& field : inequality_fields) { if (fieldsNormalized.find(field) == fieldsNormalized.end() && !field.IsKeyFieldPath()) { result->push_back(OrderBy(field, last_direction)); - } + } } // Add the document key field to the last if it is not explicitly ordered. if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == fieldsNormalized.end()) { result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); - } + } return result; } diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index d8993e7a0a6..4d7c06ac5a6 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -305,7 +305,8 @@ class Query { // The memoized list of sort orders. std::shared_ptr> CalculateNormalizedOrderBys() const; - mutable util::ThreadSafeMemoizer> memoized_normalized_order_bys_; + mutable util::ThreadSafeMemoizer> + memoized_normalized_order_bys_; // The corresponding Target of this Query instance. std::shared_ptr CalculateTarget() const; diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 470289ebe5b..09abc10b73d 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -34,14 +34,17 @@ class ThreadSafeMemoizer { public: ThreadSafeMemoizer() = default; - ThreadSafeMemoizer(const ThreadSafeMemoizer& other) : memoized_(std::atomic_load(&other.memoized_)) {} + ThreadSafeMemoizer(const ThreadSafeMemoizer& other) + : memoized_(std::atomic_load(&other.memoized_)) { + } ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); return *this; } - ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept : memoized_(std::atomic_load(&other.memoized_)) { + ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept + : memoized_(std::atomic_load(&other.memoized_)) { } ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { @@ -75,7 +78,8 @@ class ThreadSafeMemoizer { std::shared_ptr new_memoized = func(); - if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, new_memoized)) { + if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, + new_memoized)) { return *new_memoized; } } From bc0f5c92d3b6d5d7b729548f9d654b015a545a41 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 18:58:54 +0000 Subject: [PATCH 18/84] add missing include --- Firestore/core/src/core/query.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index 4d7c06ac5a6..d2b7b3247ff 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_CORE_QUERY_H_ #define FIRESTORE_CORE_SRC_CORE_QUERY_H_ +#include #include #include #include From 90a35644ac96ca07485b25b37c85dfdb67f65442 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 21:38:32 +0000 Subject: [PATCH 19/84] thread_safe_memoizer.h: use std::atomic> in c++20 --- .../core/src/util/thread_safe_memoizer.h | 162 +++++++++++++++--- .../unit/util/thread_safe_memoizer_test.cc | 1 - 2 files changed, 139 insertions(+), 24 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 09abc10b73d..299563fb109 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ #ifndef FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ #define FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ -#include #include #include @@ -25,6 +24,14 @@ namespace firebase { namespace firestore { namespace util { +// TODO(c++20): Remove the inline namespace once the other #ifdef checks for +// __cpp_lib_atomic_shared_ptr are removed. +#ifdef __cpp_lib_atomic_shared_ptr +inline namespace cpp20_atomic_shared_ptr { +#else +inline namespace cpp11_atomic_free_functions { +#endif + /** * Stores a memoized value in a manner that is safe to be shared between * multiple threads. @@ -32,45 +39,106 @@ namespace util { template class ThreadSafeMemoizer { public: - ThreadSafeMemoizer() = default; + /** + * Creates a new ThreadSafeMemoizer with no memoized value. + */ + ThreadSafeMemoizer() { + memoize_clear(memoized_); + } - ThreadSafeMemoizer(const ThreadSafeMemoizer& other) - : memoized_(std::atomic_load(&other.memoized_)) { + /** + * Copy constructor: creates a new ThreadSafeMemoizer object with the same + * memoized value as the ThreadSafeMemoizer object referred to by the given + * reference. + * + * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer + * object will share their memoized value with one another. + * + * The runtime performance of this function is O(1). + */ + ThreadSafeMemoizer(const ThreadSafeMemoizer& other) { + operator=(other); } + /** + * Copy assignment operator: replaces this object's memoized value with the + * memoized value of the ThreadSafeMemoizer object referred to by the given + * reference. + * + * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer + * object will share their memoized value with one another. If this + * ThreadSafeMemoizer object previously shared its memoized value with one or + * more other ThreadSafeMemoizer objects then that relationship is broken. + * + * The runtime performance of this function is O(1). + */ ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { - std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); + memoize_store(memoized_, other.memoized_); return *this; } - ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept - : memoized_(std::atomic_load(&other.memoized_)) { + /** + * Move constructor: creates a new ThreadSafeMemoizer object with the same + * memoized value as the ThreadSafeMemoizer object referred to by the given + * reference, also clearing its memoized value. + * + * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer + * object will NOT share their memoized value with one another; however, this + * ThreadSafeMemoizer object WILL share its memoized value with all other + * ThreadSafeMemoizer objects with which the given ThreadSafeMemoizer object + * formerly shared its memoized value. + * + * The runtime performance of this function is O(1). + */ + ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept { + operator=(std::move(other)); } + /** + * Move assignment operator: replaces this object's memoized value with the + * memoized value of the ThreadSafeMemoizer object referred to by the given + * reference, also clearing its memoized value. + * + * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer + * object will NOT share their memoized value with one another; however, this + * ThreadSafeMemoizer object WILL share its memoized value with all other + * ThreadSafeMemoizer objects with which the given ThreadSafeMemoizer object + * formerly shared its memoized value. + * + * The runtime performance of this function is O(1). + */ ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { - std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); + memoize_store(memoized_, other.memoized_); + memoize_clear(other.memoized_); return *this; } /** - * Memoize a value. + * Return the memoized value, calculating it with the given function if + * needed. * - * If there is _no_ memoized value then the given function is called to create - * the object to memoize. The created object is then stored for use in future - * invocations as the "memoized value". Finally, a reference to the created - * object is returned. + * If this object _does_ have a memoized value then this function simply + * returns a reference to it and does _not_ call the given function. * - * On the other hand, if there _is_ a memoized value, then a reference to that - * memoized value object is returned and the given function is _not_ called. + * On the other hand, if this object does _not_ have a memoized value then + * the given function is called to calculate the value to memoize. The value + * returned by the function is stored internally as the "memoized value" and + * then returned. * * The given function *must* be idempotent because it _may_ be called more - * than once due to the semantics of std::atomic::compare_exchange_weak(). + * than once due to the semantics of "weak" compare-and-exchange. No reference + * to the given function is retained by this object. The given function will + * be called synchronously by this function, if it is called at all. + * + * This function is thread-safe and may be called concurrently by multiple + * threads. * - * No reference to the given function is retained by this object, and the - * function be called synchronously, if it is called at all. + * The returned reference should only be considered "valid" as long as this + * ThreadSafeMemoizer instance is alive. */ const T& value(const std::function()>& func) { - std::shared_ptr old_memoized = std::atomic_load(&memoized_); + std::shared_ptr old_memoized = memoize_load(memoized_); + while (true) { if (old_memoized) { return *old_memoized; @@ -78,19 +146,67 @@ class ThreadSafeMemoizer { std::shared_ptr new_memoized = func(); - if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, - new_memoized)) { + if (memoize_compare_exchange(memoized_, old_memoized, new_memoized)) { return *new_memoized; } } } private: + // TODO(c++20): Remove the #ifdef checks for __cpp_lib_atomic_shared_ptr and + // delete all code that is compiled out when __cpp_lib_atomic_shared_ptr is + // defined. +#ifdef __cpp_lib_atomic_shared_ptr + std::atomic> memoized_; + + static void memoize_store(std::atomic>& memoized, + const std::shared_ptr& value) { + memoized.store(value); + } + + static std::shared_ptr memoize_load( + const std::atomic>& memoized) { + return memoized.load(); + } + + static bool memoize_compare_exchange( + std::atomic>& memoized, + std::shared_ptr& expected, + const std::shared_ptr& desired) { + return memoized.compare_exchange_weak(expected, desired); + } + +#else // #ifdef __cpp_lib_atomic_shared_ptr + // NOTE: Always use the std::atomic_XXX() functions to access this shared_ptr + // to ensure thread safety. + // See https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic. std::shared_ptr memoized_; + + static void memoize_store(std::shared_ptr& memoized, + const std::shared_ptr& value) { + std::atomic_store(&memoized, value); + } + + static std::shared_ptr memoize_load(const std::shared_ptr& memoized) { + return std::atomic_load(&memoized); + } + + static bool memoize_compare_exchange(std::shared_ptr& memoized, + std::shared_ptr& expected, + const std::shared_ptr& desired) { + return std::atomic_compare_exchange_weak(&memoized, &expected, desired); + } + +#endif // #ifdef __cpp_lib_atomic_shared_ptr + + static void memoize_clear(std::shared_ptr& memoized) { + memoize_store(memoized, std::shared_ptr()); + } }; +} // namespace cpp20_atomic_shared_ptr/cpp11_atomic_free_functions } // namespace util } // namespace firestore } // namespace firebase -#endif // FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ +#endif // FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ \ No newline at end of file diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index a4b359d25ed..fa8dc22ce43 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -17,7 +17,6 @@ #include "Firestore/core/src/util/thread_safe_memoizer.h" #include // NOLINT(build/c++11) -#include "absl/memory/memory.h" #include "gtest/gtest.h" namespace { From c376080fd4b1831772b6053099f6b0801e84b836 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 7 Jan 2025 21:54:47 +0000 Subject: [PATCH 20/84] fix lint --- Firestore/core/src/util/thread_safe_memoizer.h | 4 ++-- Firestore/core/test/unit/util/thread_safe_memoizer_test.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 299563fb109..4fdaeec57d9 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -19,6 +19,7 @@ #include #include +#include namespace firebase { namespace firestore { @@ -203,10 +204,9 @@ class ThreadSafeMemoizer { memoize_store(memoized, std::shared_ptr()); } }; - } // namespace cpp20_atomic_shared_ptr/cpp11_atomic_free_functions } // namespace util } // namespace firestore } // namespace firebase -#endif // FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ \ No newline at end of file +#endif // FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index fa8dc22ce43..7376d9c2290 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -56,4 +56,4 @@ TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { } } -} // namespace \ No newline at end of file +} // namespace From d344f17a66cae4eae00371c7add91d8eb2296384 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 17:39:25 +0000 Subject: [PATCH 21/84] thread_safe_memoizer_test.cc: starting rewrite --- .../unit/util/thread_safe_memoizer_test.cc | 62 +++++++------- .../unit/util/thread_safe_memoizer_testing.cc | 80 +++++++++++++++++++ .../unit/util/thread_safe_memoizer_testing.h | 47 +++++++++++ 3 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc create mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing.h diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 7376d9c2290..6bdc65b0545 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,44 +15,46 @@ */ #include "Firestore/core/src/util/thread_safe_memoizer.h" +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" + +#include +#include -#include // NOLINT(build/c++11) #include "gtest/gtest.h" namespace { +using firebase::firestore::testing::CountingFunc; using firebase::firestore::util::ThreadSafeMemoizer; -TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { - std::atomic global_int{77}; - - auto expensive_lambda = [&] { - // Simulate an expensive operation - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // If the lambda gets executed multiple times, threads will see incremented - // `global_int`. - ++global_int; - return std::make_shared(global_int.load()); - }; - - constexpr int num_threads = 5; - constexpr int expected_result = 78; - - // Create a thread safe memoizer and multiple threads. - ThreadSafeMemoizer memoized_result; - std::vector threads; - - for (int i = 0; i < num_threads; ++i) { - threads.emplace_back( - [&memoized_result, expected_result, &expensive_lambda] { - const int& actual_result = memoized_result.value(expensive_lambda); - // Verify that all threads get the same memoized result. - EXPECT_EQ(actual_result, expected_result); - }); +TEST(ThreadSafeMemoizerTest, DefaultConstructor) { + ThreadSafeMemoizer memoizer; + auto func = [] { return std::make_shared(42); }; + ASSERT_EQ(memoizer.value(func), 42); +} + +TEST(ThreadSafeMemoizerTest, Value_ShouldReturnComputedValueOnFirstInvocation) { + ThreadSafeMemoizer memoizer; + CountingFunc counter("rztsygzy5z"); + ASSERT_EQ(memoizer.value(counter.func()), "rztsygzy5z"); +} + +TEST(ThreadSafeMemoizerTest, + Value_ShouldReturnMemoizedValueOnSubsequentInvocations) { + ThreadSafeMemoizer memoizer; + CountingFunc counter("tfj6v4kdxn_%s"); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + ASSERT_EQ(memoizer.value(counter.func()), "tfj6v4kdxn_0"); } +} - for (auto& thread : threads) { - thread.join(); +TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { + ThreadSafeMemoizer memoizer; + CountingFunc counter("pcgx63yaa8_%s"); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + ASSERT_EQ(memoizer.value(counter.func()), "pcgx63yaa8_0"); } } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc new file mode 100644 index 00000000000..615a5d9b4ff --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -0,0 +1,80 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace firebase { +namespace firestore { +namespace testing { + +namespace { + +std::vector Split(const std::string& s, const std::string& sep) { + std::vector chunks; + auto index = s.find(sep); + decltype(index) start = 0; + while (index != std::string::npos) { + chunks.push_back(s.substr(start, index - start)); + start = index + sep.size(); + index = s.find(sep, start); + } + chunks.push_back(s.substr(start)); + return chunks; +} + +} // namespace + +CountingFunc::CountingFunc(const std::string& format) + : CountingFunc(Split(format, "%s")) { +} + +CountingFunc::CountingFunc(std::vector chunks) + : chunks_(std::move(chunks)) { + assert(!chunks_.empty()); + // Explicitly store the initial value into count_ because initialization of + // std::atomic is _not_ atomic. + count_.store(0); +} + +std::function()> CountingFunc::func() { + return [&] { return std::make_shared(Next()); }; +} + +std::string CountingFunc::Next() { + const int id = count_.fetch_add(1, std::memory_order_acq_rel); + std::ostringstream ss; + int index = 0; + for (const std::string& chunk : chunks_) { + if (index > 0) { + ss << id; + } + index++; + ss << chunk; + } + return ss.str(); +} + +} // namespace testing +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h new file mode 100644 index 00000000000..d270a77e3a6 --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ +#define FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ + +#include +#include +#include +#include +#include + +namespace firebase { +namespace firestore { +namespace testing { + +class CountingFunc { + public: + explicit CountingFunc(const std::string& format); + std::function()> func(); + + private: + std::atomic count_; + std::vector chunks_; + + explicit CountingFunc(std::vector chunks); + std::string Next(); +}; + +} // namespace testing +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ From ca8fb416c609528905a5f12bacc9fd9cf5556046 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 18:15:10 +0000 Subject: [PATCH 22/84] Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc added --- .../unit/util/thread_safe_memoizer_testing.h | 24 ++++ .../util/thread_safe_memoizer_testing_test.cc | 133 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index d270a77e3a6..44ab49750bc 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -27,9 +27,33 @@ namespace firebase { namespace firestore { namespace testing { +/** + * Generates strings that incorporate a count in a thread-safe manner. + * + * The "format" string given to the constructor is literally generated, except + * that all occurrences of "%s" are replaced with the invocation count. + * + * + */ class CountingFunc { public: + /** + * Creates a new `CountingFunc` that generates strings that match the given + * format. + * @param format the format to use when generating strings; all occurrences of + * "%s" will be replaced by the count, which starts at 0 (zero). + */ explicit CountingFunc(const std::string& format); + + /** + * Returns a function that, when invoked, generates a string using the format + * given to the constructor. Every string returned by the function has a + * different count. + * + * Although each invocation of this function _may_ return a distinct function, + * they all use the same counter and may be safely called concurrently from + * multiple threads. + */ std::function()> func(); private: diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc new file mode 100644 index 00000000000..27ac7f45d81 --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -0,0 +1,133 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +using firebase::firestore::testing::CountingFunc; + +TEST(ThreadSafeMemoizerTesting, + CountingFuncShouldReturnSameStringIfNoReplacements) { + CountingFunc counting_func("tdjebqrtny"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + EXPECT_EQ(*func(), "tdjebqrtny"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesReplacementAtStart) { + CountingFunc counting_func("%scmgb5bsbj2"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const auto i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str + "cmgb5bsbj2"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesReplacementAtEnd) { + CountingFunc counting_func("nd3krmj2mn%s"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const auto i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "nd3krmj2mn" + i_str); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesReplacementInMiddle) { + CountingFunc counting_func("txxz4%sddrs5"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const auto i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "txxz4" + i_str + "ddrs5"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesMultipleReplacements) { + CountingFunc counting_func("%scx%s3b%s5jazwf%s"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const auto i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str + "cx" + i_str + "3b" + i_str + "5jazwf" + i_str); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncFunctionsUseSameCounter) { + CountingFunc counting_func("3gswsz9hyd_%s"); + const std::vector funcs{ + counting_func.func(), counting_func.func(), counting_func.func(), + counting_func.func(), counting_func.func()}; + int next_id = 0; + for (int i = 0; i < 100; i++) { + for (decltype(funcs.size()) j = 0; j < funcs.size(); j++) { + SCOPED_TRACE("iteration i=" + std::to_string(i) + + " j=" + std::to_string(j)); + EXPECT_EQ(*funcs[j](), "3gswsz9hyd_" + std::to_string(next_id++)); + } + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncThreadSafety) { + CountingFunc counting_func("ejrxk3g6tb_%s"); + std::vector threads; + std::array, 20> strings; + std::atomic countdown(strings.size()); + for (decltype(strings.size()) i = 0; i < strings.size(); i++) { + threads.emplace_back([&, i] { + auto func = counting_func.func(); + auto& results = strings[i]; + countdown.fetch_sub(1); + while (countdown.load() > 0) { + std::this_thread::yield(); + } + for (decltype(results.size()) j = 0; j < results.size(); j++) { + results[j] = *func(); + } + }); + } + + for (auto& thread : threads) { + thread.join(); + } + + std::vector actual_strings; + for (const auto& thread_strings : strings) { + actual_strings.insert(actual_strings.end(), thread_strings.begin(), + thread_strings.end()); + } + + std::vector expected_strings; + for (decltype(actual_strings.size()) i = 0; i < actual_strings.size(); i++) { + expected_strings.push_back("ejrxk3g6tb_" + std::to_string(i)); + } + + std::sort(actual_strings.begin(), actual_strings.end()); + std::sort(expected_strings.begin(), expected_strings.end()); + ASSERT_EQ(actual_strings, expected_strings); +} + +} // namespace From 7313213976ba22cdcefd00df4c1829cf1ea5e089 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 19:22:57 +0000 Subject: [PATCH 23/84] fix regex tests --- .../core/test/unit/util/thread_safe_memoizer_test.cc | 12 ++++++++++-- .../test/unit/util/thread_safe_memoizer_testing.h | 8 ++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 6bdc65b0545..2f1ba7ff4c8 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -20,12 +20,16 @@ #include #include +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace { +using namespace std::string_literals; using firebase::firestore::testing::CountingFunc; +using firebase::firestore::testing::FST_RE_DIGIT; using firebase::firestore::util::ThreadSafeMemoizer; +using testing::MatchesRegex; TEST(ThreadSafeMemoizerTest, DefaultConstructor) { ThreadSafeMemoizer memoizer; @@ -43,18 +47,22 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldReturnMemoizedValueOnSubsequentInvocations) { ThreadSafeMemoizer memoizer; CountingFunc counter("tfj6v4kdxn_%s"); + auto func = counter.func(); for (int i = 0; i < 100; i++) { SCOPED_TRACE("iteration i=" + std::to_string(i)); - ASSERT_EQ(memoizer.value(counter.func()), "tfj6v4kdxn_0"); + const auto regex = "tfj6v4kdxn_"s + FST_RE_DIGIT + "+"; + ASSERT_THAT(memoizer.value(func), MatchesRegex(regex)); } } TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { ThreadSafeMemoizer memoizer; CountingFunc counter("pcgx63yaa8_%s"); + auto func = counter.func(); for (int i = 0; i < 100; i++) { SCOPED_TRACE("iteration i=" + std::to_string(i)); - ASSERT_EQ(memoizer.value(counter.func()), "pcgx63yaa8_0"); + const auto regex = "pcgx63yaa8_"s + FST_RE_DIGIT + "+"; + ASSERT_THAT(memoizer.value(func), MatchesRegex(regex)); } } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index 44ab49750bc..380cfaf71e0 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -23,10 +23,18 @@ #include #include +#include "gtest/gtest.h" + namespace firebase { namespace firestore { namespace testing { +#if defined(GTEST_USES_SIMPLE_RE) || defined(GTEST_USES_RE2) +constexpr const char* FST_RE_DIGIT = "\\d"; +#elif defined(GTEST_USES_POSIX_RE) +constexpr const char* FST_RE_DIGIT = "[[:digit:]]"; +#endif + /** * Generates strings that incorporate a count in a thread-safe manner. * From 5833e22b7f0c66d1eb44462043a614c7ad226618 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 19:52:48 +0000 Subject: [PATCH 24/84] cleanups --- .../unit/util/thread_safe_memoizer_test.cc | 13 +++- .../unit/util/thread_safe_memoizer_testing.cc | 4 ++ .../unit/util/thread_safe_memoizer_testing.h | 19 ++++- .../util/thread_safe_memoizer_testing_test.cc | 72 +++++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 2f1ba7ff4c8..2a0e91d214c 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -48,10 +48,17 @@ TEST(ThreadSafeMemoizerTest, ThreadSafeMemoizer memoizer; CountingFunc counter("tfj6v4kdxn_%s"); auto func = counter.func(); + + const auto expected = memoizer.value(func); + // Do not hardcode "tfj6v4kdxn_0" as the expected value because + // ThreadSafeMemoizer.value() documents that it _may_ call the given function + // multiple times. + ASSERT_THAT(memoizer.value(func), + MatchesRegex("tfj6v4kdxn_"s + FST_RE_DIGIT + "+")); + for (int i = 0; i < 100; i++) { SCOPED_TRACE("iteration i=" + std::to_string(i)); - const auto regex = "tfj6v4kdxn_"s + FST_RE_DIGIT + "+"; - ASSERT_THAT(memoizer.value(func), MatchesRegex(regex)); + ASSERT_EQ(memoizer.value(func), expected); } } @@ -61,6 +68,8 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { auto func = counter.func(); for (int i = 0; i < 100; i++) { SCOPED_TRACE("iteration i=" + std::to_string(i)); + // Do not hardcode "tfj6v4kdxn_0" because value() documents that it _may_ + // call the function multiple times. const auto regex = "pcgx63yaa8_"s + FST_RE_DIGIT + "+"; ASSERT_THAT(memoizer.value(func), MatchesRegex(regex)); } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index 615a5d9b4ff..fd2e61a41bc 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -61,6 +61,10 @@ std::function()> CountingFunc::func() { return [&] { return std::make_shared(Next()); }; } +int CountingFunc::invocation_count() const { + return count_.load(); +} + std::string CountingFunc::Next() { const int id = count_.fetch_add(1, std::memory_order_acq_rel); std::ostringstream ss; diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index 380cfaf71e0..45c7b48bbbe 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -41,10 +41,18 @@ constexpr const char* FST_RE_DIGIT = "[[:digit:]]"; * The "format" string given to the constructor is literally generated, except * that all occurrences of "%s" are replaced with the invocation count. * - * + * All functions in this class may be safely called concurrently by multiple + * threads. */ class CountingFunc { public: + /** + * Creates a new `CountingFunc` that generates strings that are equal to + * the base-10 string representation of the invocation count. + */ + CountingFunc() : CountingFunc("%s") { + } + /** * Creates a new `CountingFunc` that generates strings that match the given * format. @@ -61,9 +69,18 @@ class CountingFunc { * Although each invocation of this function _may_ return a distinct function, * they all use the same counter and may be safely called concurrently from * multiple threads. + * + * The returned function is valid as long as this `CountingFunc` object is + * valid. */ std::function()> func(); + /** + * Returns the total number of invocations that have occurred on functions + * returned by `func()`. A new instance of this class will return 0 (zero). + */ + int invocation_count() const; + private: std::atomic count_; std::vector chunks_; diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc index 27ac7f45d81..50178d9a3ca 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -26,6 +26,16 @@ namespace { using firebase::firestore::testing::CountingFunc; +TEST(ThreadSafeMemoizerTesting, DefaultConstructor) { + CountingFunc counting_func; + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const auto i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str); + } +} + TEST(ThreadSafeMemoizerTesting, CountingFuncShouldReturnSameStringIfNoReplacements) { CountingFunc counting_func("tdjebqrtny"); @@ -130,4 +140,66 @@ TEST(ThreadSafeMemoizerTesting, CountingFuncThreadSafety) { ASSERT_EQ(actual_strings, expected_strings); } +TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountOnNewInstance) { + CountingFunc counting_func; + EXPECT_EQ(counting_func.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountIncrementsBy1) { + CountingFunc counting_func; + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + EXPECT_EQ(counting_func.invocation_count(), i); + // ReSharper disable once CppExpressionWithoutSideEffects + func(); + EXPECT_EQ(counting_func.invocation_count(), i + 1); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncInvocationCountIncrementedByEachFunc) { + CountingFunc counting_func; + for (int i = 0; i < 100; i++) { + auto func = counting_func.func(); + EXPECT_EQ(counting_func.invocation_count(), i); + // ReSharper disable once CppExpressionWithoutSideEffects + func(); + EXPECT_EQ(counting_func.invocation_count(), i + 1); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountThreadSafe) { + CountingFunc counting_func; + const auto hardware_concurrency = std::thread::hardware_concurrency(); + const int num_threads = hardware_concurrency != 0 ? hardware_concurrency : 4; + std::vector threads; + std::atomic countdown{num_threads}; + for (auto i = num_threads; i > 0; i--) { + threads.emplace_back([&, i] { + auto func = counting_func.func(); + countdown.fetch_sub(1); + while (countdown.load() > 0) { + std::this_thread::yield(); + } + // ReSharper disable once CppExpressionWithoutSideEffects + auto last_count = counting_func.invocation_count(); + for (int j = 0; j < 100; j++) { + SCOPED_TRACE("Thread i=" + std::to_string(i) + + " j=" + std::to_string(j)); + // ReSharper disable once CppExpressionWithoutSideEffects + func(); + auto new_count = counting_func.invocation_count(); + EXPECT_GT(new_count, last_count); + last_count = new_count; + } + }); + } + + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_EQ(counting_func.invocation_count(), num_threads * 100); +} + } // namespace From 2c232f5a96fdbc87e7933d7cff49f931e5a4c4e2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 20:23:27 +0000 Subject: [PATCH 25/84] thread_safe_memoizer_test.cc: Value_ShouldNotInvokeTheFunctionAfterMemoizing added --- .../unit/util/thread_safe_memoizer_test.cc | 49 ++++++++++++++++--- .../unit/util/thread_safe_memoizer_testing.cc | 15 +++++- .../unit/util/thread_safe_memoizer_testing.h | 13 +++++ .../util/thread_safe_memoizer_testing_test.cc | 19 ++----- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 2a0e91d214c..a985379b6a1 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -19,6 +19,7 @@ #include #include +#include #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -26,6 +27,7 @@ namespace { using namespace std::string_literals; +using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; using firebase::firestore::testing::FST_RE_DIGIT; using firebase::firestore::util::ThreadSafeMemoizer; @@ -64,14 +66,49 @@ TEST(ThreadSafeMemoizerTest, TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { ThreadSafeMemoizer memoizer; - CountingFunc counter("pcgx63yaa8_%s"); + CountingFunc counter; auto func = counter.func(); + memoizer.value(func); + // Do not hardcode 1 as the expected invocation count because + // ThreadSafeMemoizer.value() documents that it _may_ call the given function + // multiple times. + const auto expected_invocation_count = counter.invocation_count(); for (int i = 0; i < 100; i++) { - SCOPED_TRACE("iteration i=" + std::to_string(i)); - // Do not hardcode "tfj6v4kdxn_0" because value() documents that it _may_ - // call the function multiple times. - const auto regex = "pcgx63yaa8_"s + FST_RE_DIGIT + "+"; - ASSERT_THAT(memoizer.value(func), MatchesRegex(regex)); + memoizer.value(func); + } + EXPECT_EQ(counter.invocation_count(), expected_invocation_count); +} + +TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { + ThreadSafeMemoizer memoizer; + CountingFunc counter; + auto func = counter.func(); + + const auto hardware_concurrency = std::thread::hardware_concurrency(); + const int num_threads = hardware_concurrency != 0 ? hardware_concurrency : 4; + std::vector threads; + CountDownLatch latch(num_threads); + std::atomic value_has_been_memoized{false}; + for (auto i = num_threads; i > 0; i--) { + threads.emplace_back([&, i] { + latch.arrive_and_wait(); + for (int j = 0; j < 100; j++) { + if (value_has_been_memoized.load(std::memory_order_acquire)) { + const auto invocation_count_before = counter.invocation_count(); + memoizer.value(func); + SCOPED_TRACE("thread i=" + std::to_string(i) + + " j=" + std::to_string(j)); + EXPECT_EQ(counter.invocation_count(), invocation_count_before); + } else { + memoizer.value(func); + value_has_been_memoized.store(true, std::memory_order_release); + } + } + }); + } + + for (auto& thread : threads) { + thread.join(); } } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index fd2e61a41bc..caebf05a4dd 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -22,12 +22,12 @@ #include #include #include +#include #include namespace firebase { namespace firestore { namespace testing { - namespace { std::vector Split(const std::string& s, const std::string& sep) { @@ -79,6 +79,19 @@ std::string CountingFunc::Next() { return ss.str(); } +CountDownLatch::CountDownLatch(int count) { + // Explicitly store the count into the atomic because initialization is + // NOT atomic. + count_.store(count); +} + +void CountDownLatch::arrive_and_wait() { + count_.fetch_sub(1, std::memory_order_acq_rel); + while (count_.load(std::memory_order_acquire) > 0) { + std::this_thread::yield(); + } +} + } // namespace testing } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index 45c7b48bbbe..9d9e8520d43 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -18,6 +18,7 @@ #define FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ #include +#include #include #include #include @@ -89,6 +90,18 @@ class CountingFunc { std::string Next(); }; +/** + * A simple implementation of std::latch in C++20. + */ +class CountDownLatch { + public: + explicit CountDownLatch(int count); + void arrive_and_wait(); + + private: + std::atomic count_; +}; + } // namespace testing } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc index 50178d9a3ca..4e05072e1bd 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -24,6 +24,7 @@ namespace { +using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; TEST(ThreadSafeMemoizerTesting, DefaultConstructor) { @@ -105,15 +106,12 @@ TEST(ThreadSafeMemoizerTesting, CountingFuncThreadSafety) { CountingFunc counting_func("ejrxk3g6tb_%s"); std::vector threads; std::array, 20> strings; - std::atomic countdown(strings.size()); + CountDownLatch latch(strings.size()); for (decltype(strings.size()) i = 0; i < strings.size(); i++) { threads.emplace_back([&, i] { auto func = counting_func.func(); auto& results = strings[i]; - countdown.fetch_sub(1); - while (countdown.load() > 0) { - std::this_thread::yield(); - } + latch.arrive_and_wait(); for (decltype(results.size()) j = 0; j < results.size(); j++) { results[j] = *func(); } @@ -150,7 +148,6 @@ TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountIncrementsBy1) { auto func = counting_func.func(); for (int i = 0; i < 100; i++) { EXPECT_EQ(counting_func.invocation_count(), i); - // ReSharper disable once CppExpressionWithoutSideEffects func(); EXPECT_EQ(counting_func.invocation_count(), i + 1); } @@ -162,7 +159,6 @@ TEST(ThreadSafeMemoizerTesting, for (int i = 0; i < 100; i++) { auto func = counting_func.func(); EXPECT_EQ(counting_func.invocation_count(), i); - // ReSharper disable once CppExpressionWithoutSideEffects func(); EXPECT_EQ(counting_func.invocation_count(), i + 1); } @@ -173,20 +169,15 @@ TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountThreadSafe) { const auto hardware_concurrency = std::thread::hardware_concurrency(); const int num_threads = hardware_concurrency != 0 ? hardware_concurrency : 4; std::vector threads; - std::atomic countdown{num_threads}; + CountDownLatch latch(num_threads); for (auto i = num_threads; i > 0; i--) { threads.emplace_back([&, i] { auto func = counting_func.func(); - countdown.fetch_sub(1); - while (countdown.load() > 0) { - std::this_thread::yield(); - } - // ReSharper disable once CppExpressionWithoutSideEffects + latch.arrive_and_wait(); auto last_count = counting_func.invocation_count(); for (int j = 0; j < 100; j++) { SCOPED_TRACE("Thread i=" + std::to_string(i) + " j=" + std::to_string(j)); - // ReSharper disable once CppExpressionWithoutSideEffects func(); auto new_count = counting_func.invocation_count(); EXPECT_GT(new_count, last_count); From e6c26a720387e78931ecc8a61590cb775d7e284c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 21:09:48 +0000 Subject: [PATCH 26/84] thread_safe_memoizer_test.cc: copy constructor tests --- .../core/src/util/thread_safe_memoizer.h | 20 ---------- .../unit/util/thread_safe_memoizer_test.cc | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 4fdaeec57d9..e72ef38649f 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -52,9 +52,6 @@ class ThreadSafeMemoizer { * memoized value as the ThreadSafeMemoizer object referred to by the given * reference. * - * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer - * object will share their memoized value with one another. - * * The runtime performance of this function is O(1). */ ThreadSafeMemoizer(const ThreadSafeMemoizer& other) { @@ -66,11 +63,6 @@ class ThreadSafeMemoizer { * memoized value of the ThreadSafeMemoizer object referred to by the given * reference. * - * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer - * object will share their memoized value with one another. If this - * ThreadSafeMemoizer object previously shared its memoized value with one or - * more other ThreadSafeMemoizer objects then that relationship is broken. - * * The runtime performance of this function is O(1). */ ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { @@ -83,12 +75,6 @@ class ThreadSafeMemoizer { * memoized value as the ThreadSafeMemoizer object referred to by the given * reference, also clearing its memoized value. * - * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer - * object will NOT share their memoized value with one another; however, this - * ThreadSafeMemoizer object WILL share its memoized value with all other - * ThreadSafeMemoizer objects with which the given ThreadSafeMemoizer object - * formerly shared its memoized value. - * * The runtime performance of this function is O(1). */ ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept { @@ -100,12 +86,6 @@ class ThreadSafeMemoizer { * memoized value of the ThreadSafeMemoizer object referred to by the given * reference, also clearing its memoized value. * - * This ThreadSafeMemoizer object and the given referred-to ThreadSafeMemoizer - * object will NOT share their memoized value with one another; however, this - * ThreadSafeMemoizer object WILL share its memoized value with all other - * ThreadSafeMemoizer objects with which the given ThreadSafeMemoizer object - * formerly shared its memoized value. - * * The runtime performance of this function is O(1). */ ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index a985379b6a1..bd08c6976f8 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -112,4 +112,41 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { } } +TEST(ThreadSafeMemoizerTest, + CopyConstructor_NoMemoizedValue_OriginalMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy(memoizer); + + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_EQ(memoizer_copy.value(memoizer_copy_counter.func()), "bbb"); + + EXPECT_GT(memoizer_counter.invocation_count(), 0); + EXPECT_GT(memoizer_copy_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, + CopyConstructor_NoMemoizedValue_CopyMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy(memoizer); + + EXPECT_EQ(memoizer_copy.value(memoizer_copy_counter.func()), "bbb"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + + EXPECT_GT(memoizer_counter.invocation_count(), 0); + EXPECT_GT(memoizer_copy_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, CopyConstructor_MemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + ThreadSafeMemoizer memoizer_copy(memoizer); + + EXPECT_EQ(memoizer_copy.value(memoizer_copy_counter.func()), "aaa"); + + EXPECT_EQ(memoizer_copy_counter.invocation_count(), 0); +} + } // namespace From 76f32492b772c646d971243911451466e19e66b2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 21:09:48 +0000 Subject: [PATCH 27/84] thread_safe_memoizer_test.cc: move constructor tests --- .../unit/util/thread_safe_memoizer_test.cc | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index bd08c6976f8..68063eb6574 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -114,39 +115,60 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { TEST(ThreadSafeMemoizerTest, CopyConstructor_NoMemoizedValue_OriginalMemoizesFirst) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_counter("bbb"); + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy(memoizer); + ThreadSafeMemoizer memoizer_copy_dest(memoizer); EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_EQ(memoizer_copy.value(memoizer_copy_counter.func()), "bbb"); + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); EXPECT_GT(memoizer_counter.invocation_count(), 0); - EXPECT_GT(memoizer_copy_counter.invocation_count(), 0); + EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); } TEST(ThreadSafeMemoizerTest, CopyConstructor_NoMemoizedValue_CopyMemoizesFirst) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_counter("bbb"); + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy(memoizer); + ThreadSafeMemoizer memoizer_copy_dest(memoizer); - EXPECT_EQ(memoizer_copy.value(memoizer_copy_counter.func()), "bbb"); + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); EXPECT_GT(memoizer_counter.invocation_count(), 0); - EXPECT_GT(memoizer_copy_counter.invocation_count(), 0); + EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); } TEST(ThreadSafeMemoizerTest, CopyConstructor_MemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_counter("bbb"); + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); ThreadSafeMemoizer memoizer; memoizer.value(memoizer_counter.func()); - ThreadSafeMemoizer memoizer_copy(memoizer); + ThreadSafeMemoizer memoizer_copy_dest(memoizer); - EXPECT_EQ(memoizer_copy.value(memoizer_copy_counter.func()), "aaa"); + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); - EXPECT_EQ(memoizer_copy_counter.invocation_count(), 0); + EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, MoveConstructor_NoMemoizedValue) { + CountingFunc memoizer_move_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_move_dest(std::move(memoizer)); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "bbb"); + + EXPECT_GT(memoizer_move_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, MoveConstructor_MemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + ThreadSafeMemoizer memoizer_move_dest(std::move(memoizer)); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "aaa"); + + EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); } } // namespace From 32025e717705f1c67c1a7f902ac2ab7bb71682c3 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 21:09:48 +0000 Subject: [PATCH 28/84] thread_safe_memoizer_test.cc: copy assignment operator tests --- .../unit/util/thread_safe_memoizer_test.cc | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 68063eb6574..0e15cbaf2dd 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -171,4 +171,82 @@ TEST(ThreadSafeMemoizerTest, MoveConstructor_MemoizedValue) { EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); } +TEST(ThreadSafeMemoizerTest, + CopyAssignment_NoMemoizedValueToNoMemoizedValue_OriginalMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest; + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); +} + +TEST(ThreadSafeMemoizerTest, + CopyAssignment_NoMemoizedValueToNoMemoizedValue_CopyMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest; + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToNoMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + const auto expected_memoizer_counter_invocation_count = + memoizer_counter.invocation_count(); + ThreadSafeMemoizer memoizer_copy_dest; + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_EQ(memoizer_counter.invocation_count(), + expected_memoizer_counter_invocation_count); + EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_NoMemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter1("bbb1"), + memoizer_copy_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest; + memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), + "bbb2"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter1("aaa1"), memoizer_counter2("aaa2"), + memoizer_copy_dest_counter1("bbb1"), memoizer_copy_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter1.func()); + const auto expected_memoizer_counter1_invocation_count = + memoizer_counter1.invocation_count(); + ThreadSafeMemoizer memoizer_copy_dest; + memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); + const auto expected_memoizer_copy_dest_counter1_invocation_count = + memoizer_copy_dest_counter1.invocation_count(); + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), + "aaa1"); + EXPECT_EQ(memoizer.value(memoizer_counter2.func()), "aaa1"); + EXPECT_EQ(memoizer_counter1.invocation_count(), + expected_memoizer_counter1_invocation_count); + EXPECT_EQ(memoizer_copy_dest_counter1.invocation_count(), + expected_memoizer_copy_dest_counter1_invocation_count); +} + } // namespace From ccc3b3ba7de482a7a8693ca19123a8f327218c50 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 21:09:48 +0000 Subject: [PATCH 29/84] thread_safe_memoizer_test.cc: move assignment operator tests --- .../unit/util/thread_safe_memoizer_test.cc | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 0e15cbaf2dd..895156176b0 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -249,4 +249,51 @@ TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToMemoizedValue) { expected_memoizer_copy_dest_counter1_invocation_count); } +TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToNoMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + ThreadSafeMemoizer memoizer_move_dest; + + memoizer_move_dest = std::move(memoizer); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "aaa"); + EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_NoMemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter1("bbb1"), + memoizer_move_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_move_dest; + memoizer_move_dest.value(memoizer_move_dest_counter1.func()); + + memoizer_move_dest = std::move(memoizer); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), + "bbb2"); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter1("aaa1"), memoizer_counter2("aaa2"), + memoizer_move_dest_counter1("bbb1"), memoizer_move_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter1.func()); + const auto expected_memoizer_counter1_invocation_count = + memoizer_counter1.invocation_count(); + ThreadSafeMemoizer memoizer_move_dest; + memoizer_move_dest.value(memoizer_move_dest_counter1.func()); + const auto expected_memoizer_move_dest_counter1_invocation_count = + memoizer_move_dest_counter1.invocation_count(); + + memoizer_move_dest = std::move(memoizer); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), + "aaa1"); + EXPECT_EQ(memoizer_counter1.invocation_count(), + expected_memoizer_counter1_invocation_count); + EXPECT_EQ(memoizer_move_dest_counter1.invocation_count(), + expected_memoizer_move_dest_counter1_invocation_count); +} + } // namespace From 05b074c1afe11f3cd32b904647d68ab5ff791087 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 21:09:48 +0000 Subject: [PATCH 30/84] thread_safe_memoizer_test.cc: add tests to make sure the object gets removed. --- .../unit/util/thread_safe_memoizer_test.cc | 67 +++++++++++++++++++ .../unit/util/thread_safe_memoizer_testing.h | 13 ++++ 2 files changed, 80 insertions(+) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 895156176b0..73379504f4b 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -31,6 +31,7 @@ using namespace std::string_literals; using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; using firebase::firestore::testing::FST_RE_DIGIT; +using firebase::firestore::testing::SetOnDestructor; using firebase::firestore::util::ThreadSafeMemoizer; using testing::MatchesRegex; @@ -296,4 +297,70 @@ TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToMemoizedValue) { expected_memoizer_move_dest_counter1_invocation_count); } +TEST(ThreadSafeMemoizerTest, + CopyConstructor_CopySourceKeepsMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + auto memoizer = std::make_unique>(); + memoizer->value([&] { return std::make_shared(destroyed); }); + + auto memoizer_copy_dest = + std::make_unique>(*memoizer); + + ASSERT_FALSE(destroyed.load()); + memoizer_copy_dest.reset(); + ASSERT_FALSE(destroyed.load()); + memoizer.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_CopySourceKeepsMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + auto memoizer = std::make_unique>(); + memoizer->value([&] { return std::make_shared(destroyed); }); + auto memoizer_copy_dest = + std::make_unique>(); + + *memoizer_copy_dest = *memoizer; + + ASSERT_FALSE(destroyed.load()); + memoizer_copy_dest.reset(); + ASSERT_FALSE(destroyed.load()); + memoizer.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, + MoveConstructor_MoveSourceDoesNotKeepMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + ThreadSafeMemoizer memoizer; + memoizer.value([&] { return std::make_shared(destroyed); }); + + auto memoizer_move_dest = + std::make_unique>( + std::move(memoizer)); + + ASSERT_FALSE(destroyed.load()); + memoizer_move_dest.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, + MoveAssignment_MoveSourceDoesNotKeepMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + ThreadSafeMemoizer memoizer; + memoizer.value([&] { return std::make_shared(destroyed); }); + auto memoizer_move_dest = + std::make_unique>(); + + *memoizer_move_dest = std::move(memoizer); + + ASSERT_FALSE(destroyed.load()); + memoizer_move_dest.reset(); + ASSERT_TRUE(destroyed.load()); +} + } // namespace diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index 9d9e8520d43..da0acbdceb5 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -102,6 +102,19 @@ class CountDownLatch { std::atomic count_; }; +class SetOnDestructor { + public: + SetOnDestructor(std::atomic& flag) : flag_(flag) { + } + + ~SetOnDestructor() { + flag_.store(true); + } + + private: + std::atomic& flag_; +}; + } // namespace testing } // namespace firestore } // namespace firebase From a37a37617a3c9f4b4f984ec6a0d6eedd4c4de0dd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 16:49:04 -0500 Subject: [PATCH 31/84] Firestore.xcodeproj/project.pbxproj: add missing files --- .../Firestore.xcodeproj/project.pbxproj | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 7b6e8450bf1..15abfd2adfb 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -259,6 +259,7 @@ 258B372CF33B7E7984BBA659 /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; 25C167BAA4284FC951206E1F /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; + 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 25FE27330996A59F31713A0C /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; }; 2618255E63631038B64DF3BB /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 2620644052E960310DADB298 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; @@ -400,6 +401,7 @@ 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; + 3D6AC48D6197E6539BBBD28F /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; 3DBB48F077C97200F32B51A0 /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 3DBBC644BE08B140BCC23BD5 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; @@ -432,6 +434,7 @@ 44A8B51C05538A8DACB85578 /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44C4244E42FFFB6E9D7F28BA /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44EAF3E6EAC0CC4EB2147D16 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; + 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 4562CDD90F5FF0491F07C5DA /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; 457171CE2510EEA46F7D8A30 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; @@ -452,6 +455,7 @@ 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; 47B8ED6737A24EF96B1ED318 /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; }; 4809D7ACAA9414E3192F04FF /* FIRGeoPointTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */; }; + 482D503CC826265FCEAB53DE /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 485CBA9F99771437BA1CB401 /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 48720B5768AFA2B2F3E14C04 /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 48926FF55484E996B474D32F /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = DD990FD89C165F4064B4F608 /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json */; }; @@ -783,6 +787,7 @@ 67B8C34BDF0FFD7532D7BE4F /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */; }; 67BC2B77C1CC47388E79D774 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; }; 67CF9FAA890307780731E1DA /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; + 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 6938575C8B5E6FE0D562547A /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; 6938ABD1891AD4B9FD5FE664 /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; }; 69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; }; @@ -873,6 +878,7 @@ 77C5703230DB77F0540D1F89 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */; }; 77D38E78F7CCB8504450A8FB /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; 77D3CF0BE43BC67B9A26B06D /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; + 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 784FCB02C76096DACCBA11F2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 78D99CDBB539B0AEE0029831 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 3841925AA60E13A027F565E6 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json */; }; 78E8DDDBE131F3DA9AF9F8B8 /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -991,6 +997,7 @@ 8C602DAD4E8296AB5EFB962A /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */; }; 8D0EF43F1B7B156550E65C20 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; + 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 8DBA8DC55722ED9D3A1BB2C9 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 1A7D48A017ECB54FD381D126 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json */; }; 8E103A426D6E650DC338F281 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; 8E41D53C77C30372840B0367 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 728F617782600536F2561463 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json */; }; @@ -1114,6 +1121,7 @@ A728A4D7FA17F9F3257E0002 /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8582DFD74E8060C7072104B /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json */; }; A7309DAD4A3B5334536ECA46 /* remote_event_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 584AE2C37A55B408541A6FF3 /* remote_event_test.cc */; }; A7399FB3BEC50BBFF08EC9BA /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; + A7669E72BCED7FBADA4B1314 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; A80D38096052F928B17E1504 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; A833A216988ADFD4876763CD /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; A841EEB5A94A271523EAE459 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = A5D9044B72061CAF284BC9E4 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json */; }; @@ -1268,6 +1276,7 @@ BC8DFBCB023DBD914E27AA7D /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; BCA720A0F54D23654F806323 /* ConditionalConformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3228F51DCDC2E90D5C58F97 /* ConditionalConformanceTests.swift */; }; BCAC9F7A865BD2320A4D8752 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; + BD0882A40BD8AE042629C179 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; BD3A421C9E40C57D25697E75 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BD051DBE754950FEAC7A446 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json */; }; BD6CC8614970A3D7D2CF0D49 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; BDD2D1812BAD962E3C81A53F /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; @@ -1284,6 +1293,7 @@ BFEAC4151D3AA8CE1F92CC2D /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; C02A969BF4BB63ABCB531B4B /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */; }; C06E54352661FCFB91968640 /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; + C099AEC05D44976755BA32A2 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; C09BDBA73261578F9DA74CEE /* firebase_auth_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = F869D85E900E5AF6CD02E2FC /* firebase_auth_credentials_provider_test.mm */; }; C0AD8DB5A84CAAEE36230899 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; C0EFC5FB79517679C377C252 /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; @@ -1358,6 +1368,7 @@ CE2962775B42BDEEE8108567 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; CE411D4B70353823DE63C0D5 /* bundle_loader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A853C81A6A5A51C9D0389EDA /* bundle_loader_test.cc */; }; CEA91CE103B42533C54DBAD6 /* memory_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */; }; + CF18D52A88F4F6F62C5495EF /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; CF1FB026CCB901F92B4B2C73 /* watch_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2D7472BC70C024D736FF74D9 /* watch_change_test.cc */; }; CF5DE1ED21DD0A9783383A35 /* CodableIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124C932B22C1642C00CA8C2D /* CodableIntegrationTests.swift */; }; CFA4A635ECD105D2044B3692 /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3355BE9391CC4857AF0BDAE3 /* DatabaseTests.swift */; }; @@ -1409,6 +1420,7 @@ D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; + D928302820891CCCAD0437DD /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; D94A1862B8FB778225DB54A1 /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; D98430EA4FAA357D855FA50F /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; @@ -1725,6 +1737,7 @@ 214877F52A705012D6720CA0 /* object_value_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = object_value_test.cc; sourceTree = ""; }; 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_target_cache_test.cc; sourceTree = ""; }; + 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = thread_safe_memoizer_testing.h; sourceTree = ""; }; 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = lru_garbage_collector_test.cc; sourceTree = ""; }; 28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = logic_utils_test.cc; sourceTree = ""; }; 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_document_overlay_cache_test.cc; sourceTree = ""; }; @@ -1932,6 +1945,7 @@ 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = ""; }; 6A7A30A2DB3367E08939E789 /* bloom_filter.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = bloom_filter.pb.h; sourceTree = ""; }; 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.release.xcconfig"; sourceTree = ""; }; + 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_testing.cc; sourceTree = ""; }; 6E8302DE210222ED003E1EA3 /* FSTFuzzTestFieldPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTFuzzTestFieldPath.h; sourceTree = ""; }; 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestFieldPath.mm; sourceTree = ""; }; 6EA39FDD20FE820E008D461F /* FSTFuzzTestSerializer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestSerializer.mm; sourceTree = ""; }; @@ -2109,6 +2123,7 @@ E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = ""; }; E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_target_cache_test.cc; sourceTree = ""; }; + EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_testing_test.cc; sourceTree = ""; }; ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EF3A65472C66B9560041EE69 /* FIRVectorValueTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRVectorValueTests.mm; sourceTree = ""; }; EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRAggregateTests.mm; sourceTree = ""; }; @@ -2412,6 +2427,9 @@ 899FC22684B0F7BEEAE13527 /* task_test.cc */, A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */, 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */, + 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */, + 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */, + EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */, B68B1E002213A764008977EF /* to_string_apple_test.mm */, B696858D2214B53900271095 /* to_string_test.cc */, ); @@ -4327,6 +4345,8 @@ 9B2C6A48A4DBD36080932B4E /* testing_hooks_test.cc in Sources */, 32A95242C56A1A230231DB6A /* testutil.cc in Sources */, 51018EA27CF914DD1CC79CB3 /* thread_safe_memoizer_test.cc in Sources */, + 482D503CC826265FCEAB53DE /* thread_safe_memoizer_testing.cc in Sources */, + 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB78229DECDE000FB92F /* time_testing.cc in Sources */, ACC9369843F5ED3BD2284078 /* timestamp_test.cc in Sources */, 2AAEABFD550255271E3BAC91 /* to_string_apple_test.mm in Sources */, @@ -4548,6 +4568,8 @@ 24B75C63BDCD5551B2F69901 /* testing_hooks_test.cc in Sources */, 8388418F43042605FB9BFB92 /* testutil.cc in Sources */, 5BB33F0BC7960D26062B07D3 /* thread_safe_memoizer_test.cc in Sources */, + 3D6AC48D6197E6539BBBD28F /* thread_safe_memoizer_testing.cc in Sources */, + 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB79229DECDE000FB92F /* time_testing.cc in Sources */, 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */, 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */, @@ -4793,6 +4815,8 @@ D0DA42DC66C4FE508A63B269 /* testing_hooks_test.cc in Sources */, 409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */, B31B5E0D4EA72C5916CC71F5 /* thread_safe_memoizer_test.cc in Sources */, + 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */, + 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */, 6300709ECDE8E0B5A8645F8D /* time_testing.cc in Sources */, 0CEE93636BA4852D3C5EC428 /* timestamp_test.cc in Sources */, 95DCD082374F871A86EF905F /* to_string_apple_test.mm in Sources */, @@ -5038,6 +5062,8 @@ F6738D3B72352BBEFB87172C /* testing_hooks_test.cc in Sources */, A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */, 09B83B26E47B6F6668DF54B8 /* thread_safe_memoizer_test.cc in Sources */, + CF18D52A88F4F6F62C5495EF /* thread_safe_memoizer_testing.cc in Sources */, + A7669E72BCED7FBADA4B1314 /* thread_safe_memoizer_testing_test.cc in Sources */, A25FF76DEF542E01A2DF3B0E /* time_testing.cc in Sources */, 1E42CD0F60EB22A5D0C86D1F /* timestamp_test.cc in Sources */, F9705E595FC3818F13F6375A /* to_string_apple_test.mm in Sources */, @@ -5269,6 +5295,8 @@ F184E5367DF3CA158EDE8532 /* testing_hooks_test.cc in Sources */, 54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */, 20A93AC59CD5A7AC41F10412 /* thread_safe_memoizer_test.cc in Sources */, + 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */, + BD0882A40BD8AE042629C179 /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB77229DECDE000FB92F /* time_testing.cc in Sources */, ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */, B68B1E012213A765008977EF /* to_string_apple_test.mm in Sources */, @@ -5533,6 +5561,8 @@ 5360D52DCAD1069B1E4B0B9D /* testing_hooks_test.cc in Sources */, CA989C0E6020C372A62B7062 /* testutil.cc in Sources */, 6DFD49CCE2281CE243FEBB63 /* thread_safe_memoizer_test.cc in Sources */, + D928302820891CCCAD0437DD /* thread_safe_memoizer_testing.cc in Sources */, + C099AEC05D44976755BA32A2 /* thread_safe_memoizer_testing_test.cc in Sources */, 2D220B9ABFA36CD7AC43D0A7 /* time_testing.cc in Sources */, D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */, 60260A06871DCB1A5F3448D3 /* to_string_apple_test.mm in Sources */, From 901b7b59dc1704327db702124bf02354d661730e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 17:24:22 -0500 Subject: [PATCH 32/84] cpplint.py: remove c++11 lint checks, as the floor is c++14 (and has been since #10651 which was merged almost 2 years ago) --- scripts/cpplint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 07e397599e2..c62d3476c70 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -270,7 +270,6 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', - 'build/c++11', 'build/c++14', 'build/c++tr1', 'build/deprecated', From b55ee139b39bdbcd952c13aaf792cf972c09f2a1 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 17:24:22 -0500 Subject: [PATCH 33/84] cpplint.py: remove c++11 lint checks, as the floor is c++14 (and has been since #10651 which was merged almost 2 years ago) --- scripts/cpplint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 07e397599e2..c62d3476c70 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -270,7 +270,6 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', - 'build/c++11', 'build/c++14', 'build/c++tr1', 'build/deprecated', From b84c9b6da6cfb6f654bca863ec2d082ee8579cf6 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 17:31:27 -0500 Subject: [PATCH 34/84] thread_safe_memoizer_testing.cc: add missing #include --- Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index caebf05a4dd..a85cfebff1b 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace firebase { From c46f9d233f24276fc6df251a771f2573161c93ff Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 17:32:54 -0500 Subject: [PATCH 35/84] also remove cpp14 warnings --- scripts/cpplint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index c62d3476c70..1a4ca38183d 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -270,7 +270,6 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', - 'build/c++14', 'build/c++tr1', 'build/deprecated', 'build/endif_comment', From aebe23e7e3383403c6a95bd786628f9007b7f1ff Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 8 Jan 2025 17:35:00 -0500 Subject: [PATCH 36/84] also remove c++14 warnings --- scripts/cpplint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index c62d3476c70..1a4ca38183d 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -270,7 +270,6 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', - 'build/c++14', 'build/c++tr1', 'build/deprecated', 'build/endif_comment', From 64d14b8eb3cb17da986ef0ee30329e9c9d930c29 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 9 Jan 2025 03:37:30 +0000 Subject: [PATCH 37/84] string_format_test.cc: improve robustness of NonNullPointer test to also check that the expected hex address is indeed contained in the actual formatted string. --- .../core/test/unit/util/string_format_test.cc | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Firestore/core/test/unit/util/string_format_test.cc b/Firestore/core/test/unit/util/string_format_test.cc index 1d7b05585b8..dfbcc303b99 100644 --- a/Firestore/core/test/unit/util/string_format_test.cc +++ b/Firestore/core/test/unit/util/string_format_test.cc @@ -16,6 +16,10 @@ #include "Firestore/core/src/util/string_format.h" +#include +#include +#include + #include "absl/strings/string_view.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -24,6 +28,27 @@ namespace firebase { namespace firestore { namespace util { +namespace { + +using testing::HasSubstr; +using testing::MatchesRegex; + +std::string lowercase(const std::string& str) { + std::string lower_str = str; + std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), + [](auto c) { return std::tolower(c); }); + return lower_str; +} + +template +std::string hex_address(const T* ptr) { + std::ostringstream os; + os << std::hex << reinterpret_cast(ptr); + return os.str(); +} + +} // namespace + TEST(StringFormatTest, Empty) { EXPECT_EQ("", StringFormat("")); EXPECT_EQ("", StringFormat("%s", std::string().c_str())); @@ -81,8 +106,13 @@ TEST(StringFormatTest, NullPointer) { TEST(StringFormatTest, NonNullPointer) { // pointers implicitly convert to bool. Make sure this doesn't happen here. int value = 4; - EXPECT_THAT(StringFormat("Hello %s", &value), - testing::MatchesRegex("Hello (0x)?[0123456789abcdefABCDEF]+")); + + const std::string formatted_string = StringFormat("Hello %s", &value); + + const std::string hex_address_regex = "(0x)?[0123456789abcdefABCDEF]+"; + EXPECT_THAT(formatted_string, MatchesRegex("Hello " + hex_address_regex)); + const std::string expected_hex_address = lowercase(hex_address(&value)); + EXPECT_THAT(lowercase(formatted_string), HasSubstr(expected_hex_address)); } TEST(StringFormatTest, Mixed) { From 172111724c3c186e1111f03a63efb0112d98c196 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 9 Jan 2025 21:24:01 +0000 Subject: [PATCH 38/84] string_format.h: make FormatArg final, to strongly discourage others from encountering the use-after-free bug. --- Firestore/core/src/util/string_format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firestore/core/src/util/string_format.h b/Firestore/core/src/util/string_format.h index 92676c3c7c8..cc4e9e40ea0 100644 --- a/Firestore/core/src/util/string_format.h +++ b/Firestore/core/src/util/string_format.h @@ -62,7 +62,7 @@ struct FormatChoice<5> {}; * formatting of the value as an unsigned integer. * * Otherwise the value is interpreted as anything absl::AlphaNum accepts. */ -class FormatArg : public absl::AlphaNum { +class FormatArg final : public absl::AlphaNum { public: template FormatArg(T&& value, From 72833cac1a9589cf89201ccc0cb1e21b9897712c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 9 Jan 2025 21:43:33 +0000 Subject: [PATCH 39/84] add comment about b/388888512 --- Firestore/core/src/util/string_format.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Firestore/core/src/util/string_format.h b/Firestore/core/src/util/string_format.h index cc4e9e40ea0..c440e35911f 100644 --- a/Firestore/core/src/util/string_format.h +++ b/Firestore/core/src/util/string_format.h @@ -66,6 +66,11 @@ class FormatArg final : public absl::AlphaNum { public: template FormatArg(T&& value, + // TODO(b/388888512) Remove the usage of StringifySink since it is not part + // of absl's public API. Moreover, subclassing AlphaNum is not supported + // either, so find a way to do this without these two caveats. + // See https://github.com/firebase/firebase-ios-sdk/pull/14331 for a + // partial proposal. absl::strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit) : FormatArg{std::forward(value), std::move(sink), From 68ccc04ae18176dd4567d7be5a8dcb10c7544912 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 9 Jan 2025 17:12:30 -0500 Subject: [PATCH 40/84] format code --- Firestore/core/src/util/string_format.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Firestore/core/src/util/string_format.h b/Firestore/core/src/util/string_format.h index c440e35911f..ae905787f2a 100644 --- a/Firestore/core/src/util/string_format.h +++ b/Firestore/core/src/util/string_format.h @@ -65,14 +65,15 @@ struct FormatChoice<5> {}; class FormatArg final : public absl::AlphaNum { public: template - FormatArg(T&& value, - // TODO(b/388888512) Remove the usage of StringifySink since it is not part - // of absl's public API. Moreover, subclassing AlphaNum is not supported - // either, so find a way to do this without these two caveats. - // See https://github.com/firebase/firebase-ios-sdk/pull/14331 for a - // partial proposal. - absl::strings_internal::StringifySink&& sink = - {}) // NOLINT(runtime/explicit) + FormatArg( + T&& value, + // TODO(b/388888512) Remove the usage of StringifySink since it is not + // part of absl's public API. Moreover, subclassing AlphaNum is not + // supported either, so find a way to do this without these two caveats. + // See https://github.com/firebase/firebase-ios-sdk/pull/14331 for a + // partial proposal. + absl::strings_internal::StringifySink&& sink = + {}) // NOLINT(runtime/explicit) : FormatArg{std::forward(value), std::move(sink), internal::FormatChoice<0>{}} { } From 012b8a9728578e8777860be062023ae9fa3275af Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 04:53:12 +0000 Subject: [PATCH 41/84] fix lint --- .../unit/util/thread_safe_memoizer_test.cc | 2 +- .../unit/util/thread_safe_memoizer_testing.h | 5 +- scripts/cpplint.py | 105 ++---------------- 3 files changed, 11 insertions(+), 101 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 73379504f4b..2a2c966f506 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -27,7 +27,7 @@ namespace { -using namespace std::string_literals; +using namespace std::literals::string_literals; using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; using firebase::firestore::testing::FST_RE_DIGIT; diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index da0acbdceb5..c0300d1f5d4 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -17,8 +17,9 @@ #ifndef FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ #define FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ -#include #include + +#include #include #include #include @@ -104,7 +105,7 @@ class CountDownLatch { class SetOnDestructor { public: - SetOnDestructor(std::atomic& flag) : flag_(flag) { + explicit SetOnDestructor(std::atomic& flag) : flag_(flag) { } ~SetOnDestructor() { diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 1a4ca38183d..43250d5628a 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -35,7 +35,6 @@ import math # for log import os import re -import sre_compile import string import sys import sysconfig @@ -270,7 +269,6 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', - 'build/c++tr1', 'build/deprecated', 'build/endif_comment', 'build/explicit_make_pair', @@ -282,7 +280,6 @@ 'build/include_order', 'build/include_what_you_use', 'build/namespaces_headers', - 'build/namespaces_literals', 'build/namespaces', 'build/printf_format', 'build/storage_class', @@ -1026,7 +1023,7 @@ def Match(pattern, s): # performance reasons; factoring it out into a separate function turns out # to be noticeably expensive. if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].match(s) @@ -1044,14 +1041,14 @@ def ReplaceAll(pattern, rep, s): string with replacements made (or original string if no replacements) """ if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].sub(rep, s) def Search(pattern, s): """Searches the string for the pattern, caching the compiled regexp.""" if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].search(s) @@ -5354,15 +5351,10 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, 'Did you mean "memset(%s, 0, %s)"?' % (match.group(1), match.group(2))) - if Search(r'\busing namespace\b', line): - if Search(r'\bliterals\b', line): - error(filename, linenum, 'build/namespaces_literals', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') - else: - error(filename, linenum, 'build/namespaces', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') + if Search(r'\busing namespace\b', line) and not Search(r'\b::\w+_literals\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') # Detect variable-length arrays. match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) @@ -6318,87 +6310,6 @@ def ProcessLine(filename, file_extension, clean_lines, line, for check_fn in extra_check_functions: check_fn(filename, clean_lines, line, error) -def FlagCxx11Features(filename, clean_lines, linenum, error): - """Flag those c++11 features that we only allow in certain places. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++ TR1 headers. - if include and include.group(1).startswith('tr1/'): - error(filename, linenum, 'build/c++tr1', 5, - ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) - - # Flag unapproved C++11 headers. - if include and include.group(1) in ('cfenv', - 'condition_variable', - 'fenv.h', - 'future', - 'mutex', - 'thread', - 'chrono', - 'ratio', - 'regex', - 'system_error', - ): - error(filename, linenum, 'build/c++11', 5, - ('<%s> is an unapproved C++11 header.') % include.group(1)) - - # The only place where we need to worry about C++11 keywords and library - # features in preprocessor directives is in macro definitions. - if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return - - # These are classes and free functions. The classes are always - # mentioned as std::*, but we only catch the free functions if - # they're not found by ADL. They're alphabetical by header. - for top_name in ( - # type_traits - 'alignment_of', - 'aligned_union', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++11', 5, - ('std::%s is an unapproved C++11 class or function. Send c-style ' - 'an example of where it would make your code more readable, and ' - 'they may let you use it.') % top_name) - - -def FlagCxx14Features(filename, clean_lines, linenum, error): - """Flag those C++14 features that we restrict. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++14 headers. - if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): - error(filename, linenum, 'build/c++14', 5, - ('<%s> is an unapproved C++14 header.') % include.group(1)) - - # These are classes and free functions with abseil equivalents. - for top_name in ( - # memory - 'make_unique', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++14', 5, - 'std::%s does not exist in C++11. Use absl::%s instead.' % - (top_name, top_name)) - - def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=None): """Performs lint checks and reports any errors to the given error function. @@ -6435,8 +6346,6 @@ def ProcessFileData(filename, file_extension, lines, error, ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, extra_check_functions) - FlagCxx11Features(filename, clean_lines, line, error) - FlagCxx14Features(filename, clean_lines, line, error) nesting_state.CheckCompletedBlocks(filename, error) CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) From e3aa90feac190b531003e449502a8164fe20d67b Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 04:55:51 +0000 Subject: [PATCH 42/84] cpplint.py: actually remove the c++11 and C++14 checks. --- scripts/cpplint.py | 84 ---------------------------------------------- 1 file changed, 84 deletions(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 1a4ca38183d..aaaa7fd0c66 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -270,7 +270,6 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', - 'build/c++tr1', 'build/deprecated', 'build/endif_comment', 'build/explicit_make_pair', @@ -6318,87 +6317,6 @@ def ProcessLine(filename, file_extension, clean_lines, line, for check_fn in extra_check_functions: check_fn(filename, clean_lines, line, error) -def FlagCxx11Features(filename, clean_lines, linenum, error): - """Flag those c++11 features that we only allow in certain places. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++ TR1 headers. - if include and include.group(1).startswith('tr1/'): - error(filename, linenum, 'build/c++tr1', 5, - ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) - - # Flag unapproved C++11 headers. - if include and include.group(1) in ('cfenv', - 'condition_variable', - 'fenv.h', - 'future', - 'mutex', - 'thread', - 'chrono', - 'ratio', - 'regex', - 'system_error', - ): - error(filename, linenum, 'build/c++11', 5, - ('<%s> is an unapproved C++11 header.') % include.group(1)) - - # The only place where we need to worry about C++11 keywords and library - # features in preprocessor directives is in macro definitions. - if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return - - # These are classes and free functions. The classes are always - # mentioned as std::*, but we only catch the free functions if - # they're not found by ADL. They're alphabetical by header. - for top_name in ( - # type_traits - 'alignment_of', - 'aligned_union', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++11', 5, - ('std::%s is an unapproved C++11 class or function. Send c-style ' - 'an example of where it would make your code more readable, and ' - 'they may let you use it.') % top_name) - - -def FlagCxx14Features(filename, clean_lines, linenum, error): - """Flag those C++14 features that we restrict. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++14 headers. - if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): - error(filename, linenum, 'build/c++14', 5, - ('<%s> is an unapproved C++14 header.') % include.group(1)) - - # These are classes and free functions with abseil equivalents. - for top_name in ( - # memory - 'make_unique', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++14', 5, - 'std::%s does not exist in C++11. Use absl::%s instead.' % - (top_name, top_name)) - - def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=None): """Performs lint checks and reports any errors to the given error function. @@ -6435,8 +6353,6 @@ def ProcessFileData(filename, file_extension, lines, error, ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, extra_check_functions) - FlagCxx11Features(filename, clean_lines, line, error) - FlagCxx14Features(filename, clean_lines, line, error) nesting_state.CheckCompletedBlocks(filename, error) CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) From e2031c7af4da4c71406b79606d56a2edd482973b Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 04:55:51 +0000 Subject: [PATCH 43/84] cpplint.py: remove usages of the deprecated sre_compile Python module. This library was deprecated in Python 3.11 (https://docs.python.org/3/whatsnew/3.11.html#modules) and is pending complete removal some time after Python 3.13 (https://docs.python.org/3/whatsnew/3.13.html#pending-removal-in-future-versions). --- scripts/cpplint.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index aaaa7fd0c66..a9eea9f6af8 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -35,7 +35,6 @@ import math # for log import os import re -import sre_compile import string import sys import sysconfig @@ -1025,7 +1024,7 @@ def Match(pattern, s): # performance reasons; factoring it out into a separate function turns out # to be noticeably expensive. if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].match(s) @@ -1043,14 +1042,14 @@ def ReplaceAll(pattern, rep, s): string with replacements made (or original string if no replacements) """ if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].sub(rep, s) def Search(pattern, s): """Searches the string for the pattern, caching the compiled regexp.""" if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].search(s) From 73e74cf93ac22cf1adb1542a864d5b86ea2556f8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 04:59:11 +0000 Subject: [PATCH 44/84] cpplint.py: relax checks for "using namespace" declarations for importing custom literals. For example, the standard library encourages "using namespace" like: ``` using namespace std::literals::chrono_literals (since C++14) using namespace std::literals::complex_literals (since C++14) using namespace std::literals::string_literals (since C++14) using namespace std::literals::string_view_literals (since C++17) ``` so those shouldn't result in lint errors. See https://en.cppreference.com/w/cpp/symbol_index/literals --- scripts/cpplint.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index a9eea9f6af8..43250d5628a 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -280,7 +280,6 @@ 'build/include_order', 'build/include_what_you_use', 'build/namespaces_headers', - 'build/namespaces_literals', 'build/namespaces', 'build/printf_format', 'build/storage_class', @@ -5352,15 +5351,10 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, 'Did you mean "memset(%s, 0, %s)"?' % (match.group(1), match.group(2))) - if Search(r'\busing namespace\b', line): - if Search(r'\bliterals\b', line): - error(filename, linenum, 'build/namespaces_literals', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') - else: - error(filename, linenum, 'build/namespaces', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') + if Search(r'\busing namespace\b', line) and not Search(r'\b::\w+_literals\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') # Detect variable-length arrays. match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) From bb3b0f38eb4a2f880884f382de52e8d21b90b382 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:11:57 +0000 Subject: [PATCH 45/84] .github/workflows/firestore.yml: run Firestore tests when cpplint.py is changed. --- .github/workflows/firestore.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 72280447aca..444f4196b08 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -71,6 +71,7 @@ jobs: # already trigger the check workflow. - 'scripts/binary_to_array.py' - 'scripts/build.sh' + - 'scripts/cpplint.py' - 'scripts/install_prereqs.sh' - 'scripts/localize_podfile.swift' - 'scripts/pod_lib_lint.rb' From 0b737db42e7f2aa8d9c807b066a7389f0da80f09 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:15:12 +0000 Subject: [PATCH 46/84] Revert ".github/workflows/firestore.yml: run Firestore tests when cpplint.py is changed." This reverts commit bb3b0f38eb4a2f880884f382de52e8d21b90b382. --- .github/workflows/firestore.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 444f4196b08..72280447aca 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -71,7 +71,6 @@ jobs: # already trigger the check workflow. - 'scripts/binary_to_array.py' - 'scripts/build.sh' - - 'scripts/cpplint.py' - 'scripts/install_prereqs.sh' - 'scripts/localize_podfile.swift' - 'scripts/pod_lib_lint.rb' From fb7df4715e0e5c86b1c9c6255a4a9df0eac522fa Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:29:43 +0000 Subject: [PATCH 47/84] scripts/check.sh: run lint on _ALL_ files if cpplint.py itself changed. --- scripts/check.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/check.sh b/scripts/check.sh index 59cad32aa93..4d3e9bf0ff2 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -293,8 +293,19 @@ python --version "${top_dir}/scripts/check_imports.swift" # Google C++ style + +# If cpplint.py itself changed, then run it on the entire repo. +if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]] ; then + CHECK_LINT_CHANGED=0 +elif [[ -n $(git diff --name-only "$GITHUB_BASE_REF" HEAD | grep 'scripts/check_lint.py') ]]; then + CHECK_LINT_CHANGED=1 +else + CHECK_LINT_CHANGED=0 +fi + lint_cmd=("${top_dir}/scripts/check_lint.py") -if [[ "$CHECK_DIFF" == true ]]; then +if [[ "$CHECK_DIFF" == true ]] && [[ "$CHECK_LINT_CHANGED" == 0 ]] ; then lint_cmd+=("${START_SHA}") fi + "${lint_cmd[@]}" From bd0a6d8ca1237910ccd7248932d1f3179fdab86d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:38:35 +0000 Subject: [PATCH 48/84] scripts/check.sh: fix check of base branch --- scripts/check.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check.sh b/scripts/check.sh index 4d3e9bf0ff2..bf8c1f19af8 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -295,9 +295,9 @@ python --version # Google C++ style # If cpplint.py itself changed, then run it on the entire repo. -if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]] ; then +if [[ "$GITHUB_EVENT_NAME" != pull_request ]] ; then CHECK_LINT_CHANGED=0 -elif [[ -n $(git diff --name-only "$GITHUB_BASE_REF" HEAD | grep 'scripts/check_lint.py') ]]; then +elif [[ -n $(git diff --name-only "$GITHUB_REF" HEAD | grep 'scripts/check_lint.py') ]]; then CHECK_LINT_CHANGED=1 else CHECK_LINT_CHANGED=0 From 74359764c37aaf53ca5a054cb413c8f5d6cb3e51 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:41:14 +0000 Subject: [PATCH 49/84] scripts/check.sh: more fixes --- scripts/check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check.sh b/scripts/check.sh index bf8c1f19af8..3d7bac8a208 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -297,7 +297,7 @@ python --version # If cpplint.py itself changed, then run it on the entire repo. if [[ "$GITHUB_EVENT_NAME" != pull_request ]] ; then CHECK_LINT_CHANGED=0 -elif [[ -n $(git diff --name-only "$GITHUB_REF" HEAD | grep 'scripts/check_lint.py') ]]; then +elif [[ -n $(git diff --name-only "remotes/origin/$GITHUB_BASE_REF" HEAD | grep 'scripts/check_lint.py') ]]; then CHECK_LINT_CHANGED=1 else CHECK_LINT_CHANGED=0 From 168549930a6a67fd94a157a9ce1fd74d71a19e14 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:44:23 +0000 Subject: [PATCH 50/84] scripts/check.sh: try again --- scripts/check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check.sh b/scripts/check.sh index 3d7bac8a208..453f16e2e27 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -297,7 +297,7 @@ python --version # If cpplint.py itself changed, then run it on the entire repo. if [[ "$GITHUB_EVENT_NAME" != pull_request ]] ; then CHECK_LINT_CHANGED=0 -elif [[ -n $(git diff --name-only "remotes/origin/$GITHUB_BASE_REF" HEAD | grep 'scripts/check_lint.py') ]]; then +elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/check_lint.py') ]]; then CHECK_LINT_CHANGED=1 else CHECK_LINT_CHANGED=0 From a1bf8c0290c47c384d6abc17ae96c4e5dd5a3ce1 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 05:44:23 +0000 Subject: [PATCH 51/84] scripts/check.sh: also check for changes to cpplint.py --- scripts/check.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/check.sh b/scripts/check.sh index 453f16e2e27..76184979d98 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -299,6 +299,8 @@ if [[ "$GITHUB_EVENT_NAME" != pull_request ]] ; then CHECK_LINT_CHANGED=0 elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/check_lint.py') ]]; then CHECK_LINT_CHANGED=1 +elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/cpplint.py') ]]; then + CHECK_LINT_CHANGED=1 else CHECK_LINT_CHANGED=0 fi From 55c243792f07fd79b5a5bfa0ebeced903ce552c5 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 06:02:51 +0000 Subject: [PATCH 52/84] thread_safe_memoizer.h: add comments explaining the rationale for using inline namespaces. --- Firestore/core/src/util/thread_safe_memoizer.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index e72ef38649f..1ebe9c0e714 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -25,7 +25,12 @@ namespace firebase { namespace firestore { namespace util { -// TODO(c++20): Remove the inline namespace once the other #ifdef checks for +// Put the C++11 and C++20 implementations into different inline namespaces so +// that if, by chance, parts of the code compile with different values of +// `__cpp_lib_atomic_shared_ptr` this will not result in an ODR-violation +// (at least on account of just the differing `__cpp_lib_atomic_shared_ptr`). +// +// TODO(c++20): Remove the inline namespaces once the other #ifdef checks for // __cpp_lib_atomic_shared_ptr are removed. #ifdef __cpp_lib_atomic_shared_ptr inline namespace cpp20_atomic_shared_ptr { From 694533ff45ac35976b5e63576b9b095bb6632dbe Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 06:23:13 +0000 Subject: [PATCH 53/84] .github/workflows/firestore.yml: run tests under C++20 as well --- .github/workflows/firestore.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 72280447aca..87e35eb0a49 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -115,6 +115,10 @@ jobs: strategy: matrix: os: [macos-14, ubuntu-latest] + cpp_version: [default] + include: + - os: ubuntu-latest + cpp_version: 20 env: MINT_PATH: ${{ github.workspace }}/mint @@ -127,9 +131,9 @@ jobs: uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache - key: firestore-ccache-${{ runner.os }}-${{ github.sha }} + key: firestore-ccache-${{ runner.os }}-cpp${{ matrix.cpp_version }}-${{ github.sha }} restore-keys: | - firestore-ccache-${{ runner.os }}- + firestore-ccache-${{ runner.os }}-cpp${{ matrix.cpp_version }}- - name: Cache Mint packages uses: actions/cache@v4 @@ -145,6 +149,11 @@ jobs: - name: Setup build run: scripts/install_prereqs.sh Firestore ${{ runner.os }} cmake + - name: Patch CMake files to use the custom C++ version + if: ${{ matrix.cpp_version != 'default' }} + run: | + exec sed -i -e 's/CMAKE_CXX_STANDARD\s\+[0-9]\+/CMAKE_CXX_STANDARD ${{ matrix.cpp_version }}/' cmake/compiler_setup.cmake + - name: Build and test run: | export EXPERIMENTAL_MODE=true From 954e17197553e425f6ce70ca8fd6138888ceba28 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 06:32:38 +0000 Subject: [PATCH 54/84] .github/workflows/firestore.yml: grep for CMAKE_CXX_STANDARD to provide evidence that it actually changed --- .github/workflows/firestore.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 87e35eb0a49..545b1f31dde 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -152,7 +152,10 @@ jobs: - name: Patch CMake files to use the custom C++ version if: ${{ matrix.cpp_version != 'default' }} run: | - exec sed -i -e 's/CMAKE_CXX_STANDARD\s\+[0-9]\+/CMAKE_CXX_STANDARD ${{ matrix.cpp_version }}/' cmake/compiler_setup.cmake + set -xv + grep CMAKE_CXX_STANDARD cmake/compiler_setup.cmake + sed -i -e 's/CMAKE_CXX_STANDARD\s\+[0-9]\+/CMAKE_CXX_STANDARD ${{ matrix.cpp_version }}/' cmake/compiler_setup.cmake + grep CMAKE_CXX_STANDARD cmake/compiler_setup.cmake - name: Build and test run: | From 98dfa26db156087cd583d461a351ab4e8080ccf0 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 06:47:50 +0000 Subject: [PATCH 55/84] .github/workflows/firestore.yml: improve output when patching c++ version --- .github/workflows/firestore.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 545b1f31dde..3dd7d652346 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -152,10 +152,26 @@ jobs: - name: Patch CMake files to use the custom C++ version if: ${{ matrix.cpp_version != 'default' }} run: | - set -xv - grep CMAKE_CXX_STANDARD cmake/compiler_setup.cmake + readonly BEFORE=$(grep 'CMAKE_CXX_STANDARD\s\+[0-9]' cmake/compiler_setup.cmake) + echo "BEFORE=$BEFORE" + sed -i -e 's/CMAKE_CXX_STANDARD\s\+[0-9]\+/CMAKE_CXX_STANDARD ${{ matrix.cpp_version }}/' cmake/compiler_setup.cmake - grep CMAKE_CXX_STANDARD cmake/compiler_setup.cmake + readonly AFTER=$(grep 'CMAKE_CXX_STANDARD\s\+[0-9]' cmake/compiler_setup.cmake) + echo "AFTER=$AFTER" + + if [[ $BEFORE == $AFTER ]] ; then + echo "ERROR: sed command did not actually change anything." >&2 + echo "Namely, the following line should have changed, but did not: $BEFORE" >&2 + echo "Make sure that the sed command is correct as it _should_ change the C++ version." >&2 + exit 1 + elif [[ $AFTER != *'${{ matrix.cpp_version }}'* ]] ; then + echo "ERROR: sed command did not set the correct C++ version." >&2 + echo "Namely, the following line should have contained ${{ matrix.cpp_version }} but it did not: $AFTER" >&2 + echo "Make sure that the sed command is correct as it _should_ result in the c++ version being incorporated." >&2 + exit 1 + else: + echo "C++ version changed successfully." + fi - name: Build and test run: | From 2d43959222d7ffa6ce80a656d9bda7c7a6642032 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 07:47:00 +0000 Subject: [PATCH 56/84] fix some c++20 build errors --- Firestore/core/src/api/firestore.cc | 2 +- .../core/src/util/thread_safe_memoizer.h | 12 ++-- .../unit/local/remote_document_cache_test.cc | 4 +- .../test/unit/remote/grpc_stream_tester.cc | 2 +- .../core/test/unit/remote/serializer_test.cc | 12 ++-- .../core/test/unit/testutil/utf8_testing.h | 67 +++++++++++++++++++ .../test/unit/util/iterator_adaptors_test.cc | 9 ++- 7 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 Firestore/core/test/unit/testutil/utf8_testing.h diff --git a/Firestore/core/src/api/firestore.cc b/Firestore/core/src/api/firestore.cc index 70cb975cc71..5b7cc0ce127 100644 --- a/Firestore/core/src/api/firestore.cc +++ b/Firestore/core/src/api/firestore.cc @@ -193,7 +193,7 @@ void Firestore::WaitForPendingWrites(util::StatusCallback callback) { void Firestore::ClearPersistence(util::StatusCallback callback) { worker_queue()->EnqueueEvenWhileRestricted([this, callback] { - auto MaybeCallback = [=](Status status) { + auto MaybeCallback = [=, this](Status status) { if (callback) { user_executor_->Execute([=] { callback(status); }); } diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 1ebe9c0e714..23537241749 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -150,6 +150,10 @@ class ThreadSafeMemoizer { memoized.store(value); } + static void memoize_clear(std::atomic>& memoized) { + memoize_store(memoized, std::shared_ptr()); + } + static std::shared_ptr memoize_load( const std::atomic>& memoized) { return memoized.load(); @@ -173,6 +177,10 @@ class ThreadSafeMemoizer { std::atomic_store(&memoized, value); } + static void memoize_clear(std::shared_ptr& memoized) { + memoize_store(memoized, std::shared_ptr()); + } + static std::shared_ptr memoize_load(const std::shared_ptr& memoized) { return std::atomic_load(&memoized); } @@ -184,10 +192,6 @@ class ThreadSafeMemoizer { } #endif // #ifdef __cpp_lib_atomic_shared_ptr - - static void memoize_clear(std::shared_ptr& memoized) { - memoize_store(memoized, std::shared_ptr()); - } }; } // namespace cpp20_atomic_shared_ptr/cpp11_atomic_free_functions } // namespace util diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.cc b/Firestore/core/test/unit/local/remote_document_cache_test.cc index 64918b79223..5fe97fb1462 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.cc +++ b/Firestore/core/test/unit/local/remote_document_cache_test.cc @@ -122,7 +122,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadADocument) { } TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { - persistence_->Run("test_set_and_read_several_documents", [=] { + persistence_->Run("test_set_and_read_several_documents", [=, this] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), @@ -136,7 +136,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocumentsIncludingMissingDocument) { persistence_->Run( - "test_set_and_read_several_documents_including_missing_document", [=] { + "test_set_and_read_several_documents_including_missing_document", [=, this] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), diff --git a/Firestore/core/test/unit/remote/grpc_stream_tester.cc b/Firestore/core/test/unit/remote/grpc_stream_tester.cc index f59a4e8eec1..81b63ef7f2c 100644 --- a/Firestore/core/test/unit/remote/grpc_stream_tester.cc +++ b/Firestore/core/test/unit/remote/grpc_stream_tester.cc @@ -187,7 +187,7 @@ std::future FakeGrpcQueue::KeepPolling( const CompletionCallback& callback) { current_promise_ = {}; - dedicated_executor_->Execute([=] { + dedicated_executor_->Execute([=, this] { bool done = false; while (!done) { auto* completion = ExtractCompletion(); diff --git a/Firestore/core/test/unit/remote/serializer_test.cc b/Firestore/core/test/unit/remote/serializer_test.cc index 14b08b1e13f..6bbd6ba1888 100644 --- a/Firestore/core/test/unit/remote/serializer_test.cc +++ b/Firestore/core/test/unit/remote/serializer_test.cc @@ -61,6 +61,7 @@ #include "Firestore/core/test/unit/nanopb/nanopb_testing.h" #include "Firestore/core/test/unit/testutil/status_testing.h" #include "Firestore/core/test/unit/testutil/testutil.h" +#include "Firestore/core/test/unit/testutil/utf8_testing.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "google/protobuf/stubs/common.h" @@ -121,6 +122,7 @@ using testutil::OrderBy; using testutil::OrFilters; using testutil::Query; using testutil::Ref; +using testutil::StringFromU8String; using testutil::Value; using testutil::Version; using util::Status; @@ -660,19 +662,21 @@ TEST_F(SerializerTest, EncodesString) { "", "a", "abc def", - u8"æ", + StringFromU8String(u8"æ"), // Note: Each one of the three embedded universal character names // (\u-escaped) maps to three chars, so the total length of the string // literal is 10 (ignoring the terminating null), and the resulting string // literal is the same as '\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf'". The // size of 10 must be added, or else std::string will see the \0 at the // start and assume that's the end of the string. - {u8"\0\ud7ff\ue000\uffff", 10}, + StringFromU8String(u8"\0\ud7ff\ue000\uffff", 10), {"\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf", 10}, - u8"(╯°□°)╯︵ ┻━┻", + StringFromU8String(u8"(╯°□°)╯︵ ┻━┻"), }; - for (const std::string& string_value : cases) { + for (decltype(cases.size()) i = 0; i < cases.size(); ++i) { + const std::string& string_value = cases[i]; + SCOPED_TRACE("iteration " + std::to_string(i) + ": string_value=" + string_value); Message model = Value(string_value); ExpectRoundTrip(model, ValueProto(string_value), TypeOrder::kString); } diff --git a/Firestore/core/test/unit/testutil/utf8_testing.h b/Firestore/core/test/unit/testutil/utf8_testing.h new file mode 100644 index 00000000000..c40b284294d --- /dev/null +++ b/Firestore/core/test/unit/testutil/utf8_testing.h @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_UNIT_TESTUTIL_UTF8_TESTING_H_ +#define FIRESTORE_CORE_TEST_UNIT_TESTUTIL_UTF8_TESTING_H_ + +#include + +namespace firebase { +namespace firestore { +namespace testutil { + +// TODO(c++20): Remove the #if check below and delete the #else block. +#if __cplusplus >= 202002L + +// Creates a std::string whose contents are the bytes of the given null +// terminated string. +// e.g. std::string s = StringFromU8String(u8"foobar"); +constexpr std::string StringFromU8String(const char8_t* s) { + const std::u8string u8s(s); + return std::string(u8s.begin(), u8s.end()); +} + +// Creates a std::string whose contents are the first count bytes of the given +// null terminated string. +// e.g. std::string s = StringFromU8String(u8"foobar", 4); +constexpr std::string StringFromU8String(const char8_t* s, std::string::size_type count) { + const std::u8string u8s(s, count); + return std::string(u8s.begin(), u8s.end()); +} + +#else // __cplusplus >= 202002L + +// Creates a std::string whose contents are the bytes of the given null +// terminated string. +// e.g. std::string s = StringFromU8String(u8"foobar"); +constexpr std::string StringFromU8String(const char* s) { + return std::string(s); +} + +// Creates a std::string whose contents are the first count bytes of the given +// null terminated string. +// e.g. std::string s = StringFromU8String(u8"foobar", 4); +constexpr std::string StringFromU8String(const char* s, std::string::size_type count) { + return std::string(s, count); +} + +#endif // __cplusplus >= 202002L + +} // namespace testutil +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_TEST_UNIT_TESTUTIL_UTF8_TESTING_H_ diff --git a/Firestore/core/test/unit/util/iterator_adaptors_test.cc b/Firestore/core/test/unit/util/iterator_adaptors_test.cc index 6774d3b260f..938ca312984 100644 --- a/Firestore/core/test/unit/util/iterator_adaptors_test.cc +++ b/Firestore/core/test/unit/util/iterator_adaptors_test.cc @@ -28,6 +28,7 @@ #include #include +#include "Firestore/core/src/util/warnings.h" #include "absl/base/macros.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -81,8 +82,11 @@ class IteratorAdaptorTest : public testing::Test { virtual void TearDown() { } + // Suppress C++20 warning: struct std::iterator is deprecated + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() template class InlineStorageIter : public std::iterator { + SUPPRESS_END() public: T* operator->() const { return get(); @@ -567,9 +571,12 @@ TEST_F(IteratorAdaptorTest, IteratorPtrHasRandomAccessMethods) { EXPECT_EQ(88, value2); } +// Suppress C++20 warning: struct std::iterator is deprecated +SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() class MyInputIterator : public std::iterator { - public: +SUPPRESS_END() + public: explicit MyInputIterator(int* x) : x_(x) { } const int* operator*() const { From 57d60cf1b008e8db21cc7701f3bca27400818413 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 07:47:35 +0000 Subject: [PATCH 57/84] .github/workflows/firestore.yml: run tests with c++17, in addition to c++20 --- .github/workflows/firestore.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 3dd7d652346..47f1254a051 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -117,6 +117,8 @@ jobs: os: [macos-14, ubuntu-latest] cpp_version: [default] include: + - os: ubuntu-latest + cpp_version: 17 - os: ubuntu-latest cpp_version: 20 From 713ae35cb884ff9b27a29fbc066394cd895519b1 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 07:52:54 +0000 Subject: [PATCH 58/84] utf8_testing.h: remove constexpr from StringFromU8String return type, since it doesn't work in c++14 and c++20. --- Firestore/core/test/unit/testutil/utf8_testing.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firestore/core/test/unit/testutil/utf8_testing.h b/Firestore/core/test/unit/testutil/utf8_testing.h index c40b284294d..6d92fa00dcf 100644 --- a/Firestore/core/test/unit/testutil/utf8_testing.h +++ b/Firestore/core/test/unit/testutil/utf8_testing.h @@ -47,14 +47,14 @@ constexpr std::string StringFromU8String(const char8_t* s, std::string::size_typ // Creates a std::string whose contents are the bytes of the given null // terminated string. // e.g. std::string s = StringFromU8String(u8"foobar"); -constexpr std::string StringFromU8String(const char* s) { +inline std::string StringFromU8String(const char* s) { return std::string(s); } // Creates a std::string whose contents are the first count bytes of the given // null terminated string. // e.g. std::string s = StringFromU8String(u8"foobar", 4); -constexpr std::string StringFromU8String(const char* s, std::string::size_type count) { +inline std::string StringFromU8String(const char* s, std::string::size_type count) { return std::string(s, count); } From cd059dc24c081ef68ac347a3b065a065e884be07 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 08:14:19 +0000 Subject: [PATCH 59/84] .github/workflows/firestore.yml: run on c++17 and c++23, as well as with both clang and gcc --- .github/workflows/firestore.yml | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 47f1254a051..6ad713d33b9 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -114,13 +114,12 @@ jobs: (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') strategy: matrix: - os: [macos-14, ubuntu-latest] - cpp_version: [default] + os: [ubuntu-latest] + cpp_version: [default, 17, 20, 23] + compiler: [gcc, clang] include: - - os: ubuntu-latest - cpp_version: 17 - - os: ubuntu-latest - cpp_version: 20 + - os: macos-14 + cpp_version: default env: MINT_PATH: ${{ github.workspace }}/mint @@ -133,9 +132,9 @@ jobs: uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache - key: firestore-ccache-${{ runner.os }}-cpp${{ matrix.cpp_version }}-${{ github.sha }} + key: firestore-ccache-${{ runner.os }}-cpp=${{ matrix.cpp_version }}-compiler=${{ matrix.compiler }}-${{ github.sha }} restore-keys: | - firestore-ccache-${{ runner.os }}-cpp${{ matrix.cpp_version }}- + firestore-ccache-${{ runner.os }}-cpp=${{ matrix.cpp_version }}-compiler=${{ matrix.compiler }}- - name: Cache Mint packages uses: actions/cache@v4 @@ -175,6 +174,25 @@ jobs: echo "C++ version changed successfully." fi + - name: Set CC and CXX environment variables + run: | + if [[ '${{ matrix.compiler }}' == 'gcc' ]] ; then + echo "CC=gcc" >> $GITHUB_ENV + echo "CXX=g++" >> $GITHUB_ENV + gcc --version + g++ --version + elif [[ '${{ matrix.compiler }}' == 'clang' ]] ; then + echo "CC=clang" >> $GITHUB_ENV + echo "CXX=clang++" >> $GITHUB_ENV + clang --version + clang++ --version + else + cc --version + c++ --version + fi + + env | grep 'CC=\|CXX=' + - name: Build and test run: | export EXPERIMENTAL_MODE=true From dbc68329499c09164f3f6f97823de6cf72a297bb Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 08:16:43 +0000 Subject: [PATCH 60/84] format code --- .../core/test/unit/local/remote_document_cache_test.cc | 3 ++- Firestore/core/test/unit/remote/serializer_test.cc | 3 ++- Firestore/core/test/unit/testutil/utf8_testing.h | 8 +++++--- Firestore/core/test/unit/util/iterator_adaptors_test.cc | 6 +++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.cc b/Firestore/core/test/unit/local/remote_document_cache_test.cc index 5fe97fb1462..43399e41f6e 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.cc +++ b/Firestore/core/test/unit/local/remote_document_cache_test.cc @@ -136,7 +136,8 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocumentsIncludingMissingDocument) { persistence_->Run( - "test_set_and_read_several_documents_including_missing_document", [=, this] { + "test_set_and_read_several_documents_including_missing_document", + [=, this] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), diff --git a/Firestore/core/test/unit/remote/serializer_test.cc b/Firestore/core/test/unit/remote/serializer_test.cc index 6bbd6ba1888..1a6c14060ab 100644 --- a/Firestore/core/test/unit/remote/serializer_test.cc +++ b/Firestore/core/test/unit/remote/serializer_test.cc @@ -676,7 +676,8 @@ TEST_F(SerializerTest, EncodesString) { for (decltype(cases.size()) i = 0; i < cases.size(); ++i) { const std::string& string_value = cases[i]; - SCOPED_TRACE("iteration " + std::to_string(i) + ": string_value=" + string_value); + SCOPED_TRACE("iteration " + std::to_string(i) + + ": string_value=" + string_value); Message model = Value(string_value); ExpectRoundTrip(model, ValueProto(string_value), TypeOrder::kString); } diff --git a/Firestore/core/test/unit/testutil/utf8_testing.h b/Firestore/core/test/unit/testutil/utf8_testing.h index 6d92fa00dcf..28cd4d3a425 100644 --- a/Firestore/core/test/unit/testutil/utf8_testing.h +++ b/Firestore/core/test/unit/testutil/utf8_testing.h @@ -37,12 +37,13 @@ constexpr std::string StringFromU8String(const char8_t* s) { // Creates a std::string whose contents are the first count bytes of the given // null terminated string. // e.g. std::string s = StringFromU8String(u8"foobar", 4); -constexpr std::string StringFromU8String(const char8_t* s, std::string::size_type count) { +constexpr std::string StringFromU8String(const char8_t* s, + std::string::size_type count) { const std::u8string u8s(s, count); return std::string(u8s.begin(), u8s.end()); } -#else // __cplusplus >= 202002L +#else // __cplusplus >= 202002L // Creates a std::string whose contents are the bytes of the given null // terminated string. @@ -54,7 +55,8 @@ inline std::string StringFromU8String(const char* s) { // Creates a std::string whose contents are the first count bytes of the given // null terminated string. // e.g. std::string s = StringFromU8String(u8"foobar", 4); -inline std::string StringFromU8String(const char* s, std::string::size_type count) { +inline std::string StringFromU8String(const char* s, + std::string::size_type count) { return std::string(s, count); } diff --git a/Firestore/core/test/unit/util/iterator_adaptors_test.cc b/Firestore/core/test/unit/util/iterator_adaptors_test.cc index 938ca312984..1d034ffbbd6 100644 --- a/Firestore/core/test/unit/util/iterator_adaptors_test.cc +++ b/Firestore/core/test/unit/util/iterator_adaptors_test.cc @@ -86,7 +86,7 @@ class IteratorAdaptorTest : public testing::Test { SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() template class InlineStorageIter : public std::iterator { - SUPPRESS_END() + SUPPRESS_END() public: T* operator->() const { return get(); @@ -575,8 +575,8 @@ TEST_F(IteratorAdaptorTest, IteratorPtrHasRandomAccessMethods) { SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() class MyInputIterator : public std::iterator { -SUPPRESS_END() - public: + SUPPRESS_END() + public: explicit MyInputIterator(int* x) : x_(x) { } const int* operator*() const { From 899a256905bb098efa8b929cbc48f336cc2c9bdd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 08:19:55 +0000 Subject: [PATCH 61/84] utf8_testing.h: fix copyright header --- Firestore/core/test/unit/testutil/utf8_testing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firestore/core/test/unit/testutil/utf8_testing.h b/Firestore/core/test/unit/testutil/utf8_testing.h index 28cd4d3a425..07d4b1a6dfc 100644 --- a/Firestore/core/test/unit/testutil/utf8_testing.h +++ b/Firestore/core/test/unit/testutil/utf8_testing.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From dbb11b7ebf68007bfd4c101972dff1bba279c263 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 08:26:52 +0000 Subject: [PATCH 62/84] .github/workflows/firestore.yml: fix `env` completing with non-zero exit code, causing the entire step to fail. --- .github/workflows/firestore.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 6ad713d33b9..15b1b3cea77 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -176,23 +176,25 @@ jobs: - name: Set CC and CXX environment variables run: | + echo 'matrix.compiler=${{ matrix.compiler }}' if [[ '${{ matrix.compiler }}' == 'gcc' ]] ; then + echo 'Setting CC and CXX for gcc' echo "CC=gcc" >> $GITHUB_ENV echo "CXX=g++" >> $GITHUB_ENV gcc --version g++ --version elif [[ '${{ matrix.compiler }}' == 'clang' ]] ; then + echo 'Setting CC and CXX for clang' echo "CC=clang" >> $GITHUB_ENV echo "CXX=clang++" >> $GITHUB_ENV clang --version clang++ --version else + echo 'Setting neither CC nor CXX' cc --version c++ --version fi - env | grep 'CC=\|CXX=' - - name: Build and test run: | export EXPERIMENTAL_MODE=true From 83ba2fcfa55516aa82cba6c9cdfdee63aa5fc734 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 08:53:28 +0000 Subject: [PATCH 63/84] fix some warnings: explicit capture of 'this' with a capture default of '=' is a C++20 extension [-Werror,-Wc++20-extensions] --- Firestore/core/src/api/firestore.cc | 2 +- Firestore/core/test/unit/local/remote_document_cache_test.cc | 4 ++-- Firestore/core/test/unit/remote/grpc_stream_tester.cc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firestore/core/src/api/firestore.cc b/Firestore/core/src/api/firestore.cc index 5b7cc0ce127..30ddf96388f 100644 --- a/Firestore/core/src/api/firestore.cc +++ b/Firestore/core/src/api/firestore.cc @@ -193,7 +193,7 @@ void Firestore::WaitForPendingWrites(util::StatusCallback callback) { void Firestore::ClearPersistence(util::StatusCallback callback) { worker_queue()->EnqueueEvenWhileRestricted([this, callback] { - auto MaybeCallback = [=, this](Status status) { + auto MaybeCallback = [this, callback](Status status) { if (callback) { user_executor_->Execute([=] { callback(status); }); } diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.cc b/Firestore/core/test/unit/local/remote_document_cache_test.cc index 43399e41f6e..1ad906152e3 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.cc +++ b/Firestore/core/test/unit/local/remote_document_cache_test.cc @@ -122,7 +122,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadADocument) { } TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { - persistence_->Run("test_set_and_read_several_documents", [=, this] { + persistence_->Run("test_set_and_read_several_documents", [this] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), @@ -137,7 +137,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocumentsIncludingMissingDocument) { persistence_->Run( "test_set_and_read_several_documents_including_missing_document", - [=, this] { + [this] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), diff --git a/Firestore/core/test/unit/remote/grpc_stream_tester.cc b/Firestore/core/test/unit/remote/grpc_stream_tester.cc index 81b63ef7f2c..eed2275e21f 100644 --- a/Firestore/core/test/unit/remote/grpc_stream_tester.cc +++ b/Firestore/core/test/unit/remote/grpc_stream_tester.cc @@ -187,7 +187,7 @@ std::future FakeGrpcQueue::KeepPolling( const CompletionCallback& callback) { current_promise_ = {}; - dedicated_executor_->Execute([=, this] { + dedicated_executor_->Execute([this, callback] { bool done = false; while (!done) { auto* completion = ExtractCompletion(); From 472ee58dc574b244e5fc37d926c2f52919e13575 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 08:55:49 +0000 Subject: [PATCH 64/84] format code --- Firestore/core/test/unit/local/remote_document_cache_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.cc b/Firestore/core/test/unit/local/remote_document_cache_test.cc index 1ad906152e3..e0bd10917d9 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.cc +++ b/Firestore/core/test/unit/local/remote_document_cache_test.cc @@ -136,8 +136,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocumentsIncludingMissingDocument) { persistence_->Run( - "test_set_and_read_several_documents_including_missing_document", - [this] { + "test_set_and_read_several_documents_including_missing_document", [this] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), From dabc396210b110209e5c01a585f361fd64a12cb5 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 16:59:36 +0000 Subject: [PATCH 65/84] .github/workflows/firestore.yml: remove testing in c++23, since it is extraneous at this point. --- .github/workflows/firestore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 15b1b3cea77..8c3a9282c70 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -115,7 +115,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - cpp_version: [default, 17, 20, 23] + cpp_version: [default, 17, 20] compiler: [gcc, clang] include: - os: macos-14 From b3366bfca0a2dd9cf4e1f2e9a66394698bb1ad43 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 12:37:33 -0500 Subject: [PATCH 66/84] cmake/compiler_setup.cmake: clean up warnings to be more practical --- cmake/compiler_setup.cmake | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cmake/compiler_setup.cmake b/cmake/compiler_setup.cmake index f214c55ca66..2466cc901ee 100644 --- a/cmake/compiler_setup.cmake +++ b/cmake/compiler_setup.cmake @@ -45,10 +45,21 @@ if(CXX_CLANG OR CXX_GNU) -Wuninitialized -fno-common + # Don't fail the build solely on account of using deprecated things. + -Wno-error=deprecated-declarations + # Delete unused things -Wunused-function -Wunused-value -Wunused-variable ) + # Disable treating "redundant-move" as an error since it's not really a problem, + # and is even a valid coding style to over-use std::move() in case the type is + # ever changed to become non-trivially moveable. + CHECK_CXX_COMPILER_FLAG("-Wno-error=redundant-move" FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) + if(FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) + list(APPEND common_flags -Wno-error=redundant-move) + endif() + set( cxx_flags -Wreorder -Werror=reorder @@ -78,14 +89,6 @@ if(CXX_CLANG OR CXX_GNU) list(APPEND common_flags -fdiagnostics-color) endif() endif() - - # Disable treating "redundant-move" as an error since it's not really a problem, - # and is even a valid coding style to over-use std::move() in case the type is - # ever changed to become non-trivially moveable. - CHECK_CXX_COMPILER_FLAG("-Wno-error=redundant-move" FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) - if(FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) - list(APPEND common_flags -Wno-error=redundant-move) - endif() endif() if(APPLE) From 10ebc755362aabdb9415276ca9fea12228769f0d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 17:42:40 +0000 Subject: [PATCH 67/84] cmake/compiler_setup.cmake: disable redundant-move altogether --- cmake/compiler_setup.cmake | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmake/compiler_setup.cmake b/cmake/compiler_setup.cmake index 2466cc901ee..3c74d4698ac 100644 --- a/cmake/compiler_setup.cmake +++ b/cmake/compiler_setup.cmake @@ -52,12 +52,11 @@ if(CXX_CLANG OR CXX_GNU) -Wunused-function -Wunused-value -Wunused-variable ) - # Disable treating "redundant-move" as an error since it's not really a problem, - # and is even a valid coding style to over-use std::move() in case the type is - # ever changed to become non-trivially moveable. - CHECK_CXX_COMPILER_FLAG("-Wno-error=redundant-move" FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) - if(FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) - list(APPEND common_flags -Wno-error=redundant-move) + # Disable "redundant-move" warning since it's not really a problem, and is even a valid coding + # style to over-use std::move() in case the type is ever changed to become non-trivially moveable. + CHECK_CXX_COMPILER_FLAG("-Wno-redundant-move" FIREBASE_CXX_COMPILER_FLAG_NO_REDUNDANT_MOVE_SUPPORTED) + if(FIREBASE_CXX_COMPILER_FLAG_NO_REDUNDANT_MOVE_SUPPORTED) + list(APPEND common_flags -Wno-redundant-move) endif() set( From 4608f23f274d65cf2fe314c73d8c730a99547b29 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 14:07:11 -0500 Subject: [PATCH 68/84] thread_safe_memoizer.h: fix data race --- Firestore/core/src/util/thread_safe_memoizer.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 23537241749..2e0e928b9f9 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -71,7 +71,7 @@ class ThreadSafeMemoizer { * The runtime performance of this function is O(1). */ ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { - memoize_store(memoized_, other.memoized_); + memoize_store(memoized_, memoize_load(other.memoized_)); return *this; } @@ -94,7 +94,7 @@ class ThreadSafeMemoizer { * The runtime performance of this function is O(1). */ ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { - memoize_store(memoized_, other.memoized_); + memoize_store(memoized_, memoize_load(other.memoized_)); memoize_clear(other.memoized_); return *this; } @@ -151,7 +151,7 @@ class ThreadSafeMemoizer { } static void memoize_clear(std::atomic>& memoized) { - memoize_store(memoized, std::shared_ptr()); + memoized.store(std::shared_ptr()); } static std::shared_ptr memoize_load( @@ -178,7 +178,7 @@ class ThreadSafeMemoizer { } static void memoize_clear(std::shared_ptr& memoized) { - memoize_store(memoized, std::shared_ptr()); + std::atomic_store(&memoized, std::shared_ptr()); } static std::shared_ptr memoize_load(const std::shared_ptr& memoized) { From b386b40b65377738f1803297b2aa1b9dde667b9f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 14:09:47 -0500 Subject: [PATCH 69/84] thread_safe_memoizer_test.cc: add TSAN-specific tests: TSAN_ConcurrentCallsToValueShouldNotDataRace and TSAN_ValueInACopyShouldNotDataRace --- .../unit/util/thread_safe_memoizer_test.cc | 52 ++++++++++++++++++- .../unit/util/thread_safe_memoizer_testing.cc | 6 +++ .../unit/util/thread_safe_memoizer_testing.h | 9 ++++ .../util/thread_safe_memoizer_testing_test.cc | 4 +- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 2a2c966f506..d7d40b8eb8e 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -15,13 +15,13 @@ */ #include "Firestore/core/src/util/thread_safe_memoizer.h" -#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" #include #include #include #include +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -31,6 +31,7 @@ using namespace std::literals::string_literals; using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; using firebase::firestore::testing::FST_RE_DIGIT; +using firebase::firestore::testing::max_practical_parallel_threads_for_testing; using firebase::firestore::testing::SetOnDestructor; using firebase::firestore::util::ThreadSafeMemoizer; using testing::MatchesRegex; @@ -363,4 +364,53 @@ TEST(ThreadSafeMemoizerTest, ASSERT_TRUE(destroyed.load()); } +TEST(ThreadSafeMemoizerTest, TSAN_ConcurrentCallsToValueShouldNotDataRace) { + ThreadSafeMemoizer memoizer; + const auto num_threads = max_practical_parallel_threads_for_testing() * 4; + CountDownLatch latch(num_threads); + std::vector threads; + for (auto i = num_threads; i > 0; --i) { + threads.emplace_back([i, &latch, &memoizer] { + latch.arrive_and_wait(); + memoizer.value([i] { return std::make_shared(i); }); + }); + } + for (auto&& thread : threads) { + thread.join(); + } +} + +TEST(ThreadSafeMemoizerTest, TSAN_ValueInACopyShouldNotDataRace) { + ThreadSafeMemoizer memoizer; + memoizer.value([&] { return std::make_shared(1111); }); + std::unique_ptr> memoizer_copy; + // NOTE: Always use std::memory_order_relaxed when loading from and storing + // into this variable to avoid creating a happens-before releationship, which + // would defeat the purpose of this test. + std::atomic*> memoizer_copy_atomic(nullptr); + + std::thread thread1([&] { + memoizer_copy = std::make_unique>(memoizer); + memoizer_copy_atomic.store(memoizer_copy.get(), std::memory_order_relaxed); + }); + std::thread thread2([&] { + ThreadSafeMemoizer* memoizer_ptr = nullptr; + while (true) { + memoizer_ptr = memoizer_copy_atomic.load(std::memory_order_relaxed); + if (memoizer_ptr) { + break; + } + std::this_thread::yield(); + } + memoizer_ptr->value([&] { return std::make_shared(2222); }); + }); + + thread1.join(); + thread2.join(); + + const auto memoizer_copy_value = + memoizer_copy->value([&] { return std::make_shared(3333); }); + EXPECT_EQ(memoizer_copy_value, 1111); +} + } // namespace diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index a85cfebff1b..5557c6647c6 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -93,6 +93,12 @@ void CountDownLatch::arrive_and_wait() { } } +decltype(std::thread::hardware_concurrency()) +max_practical_parallel_threads_for_testing() { + const auto hardware_concurrency = std::thread::hardware_concurrency(); + return hardware_concurrency != 0 ? hardware_concurrency : 4; +} + } // namespace testing } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index c0300d1f5d4..298aac532c8 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "gtest/gtest.h" @@ -116,6 +117,14 @@ class SetOnDestructor { std::atomic& flag_; }; +/** + * Returns the largest number of threads that can be truly executed in parallel, + * or an arbitrary value greater than one if the number of CPU cores cannot be + * determined. + */ +decltype(std::thread::hardware_concurrency()) +max_practical_parallel_threads_for_testing(); + } // namespace testing } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc index 4e05072e1bd..15bce14b419 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -26,6 +26,7 @@ namespace { using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; +using firebase::firestore::testing::max_practical_parallel_threads_for_testing; TEST(ThreadSafeMemoizerTesting, DefaultConstructor) { CountingFunc counting_func; @@ -166,8 +167,7 @@ TEST(ThreadSafeMemoizerTesting, TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountThreadSafe) { CountingFunc counting_func; - const auto hardware_concurrency = std::thread::hardware_concurrency(); - const int num_threads = hardware_concurrency != 0 ? hardware_concurrency : 4; + const int num_threads = max_practical_parallel_threads_for_testing(); std::vector threads; CountDownLatch latch(num_threads); for (auto i = num_threads; i > 0; i--) { From 686db4c02dda5703d294411382cfc82542acb9ba Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 20:46:50 +0000 Subject: [PATCH 70/84] lru_garbage_collector.cc: fix c++20 build error relating to inconsisten constinit in declaration and definition --- Firestore/core/src/local/lru_garbage_collector.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firestore/core/src/local/lru_garbage_collector.cc b/Firestore/core/src/local/lru_garbage_collector.cc index 03f191f8609..b212dd3616e 100644 --- a/Firestore/core/src/local/lru_garbage_collector.cc +++ b/Firestore/core/src/local/lru_garbage_collector.cc @@ -89,7 +89,7 @@ class RollingSequenceNumberBuffer { } // namespace -const ListenSequenceNumber kListenSequenceNumberInvalid = -1; +ABSL_CONST_INIT const ListenSequenceNumber kListenSequenceNumberInvalid = -1; LruParams LruParams::Default() { return LruParams{100 * 1024 * 1024, 10, 1000}; From 03cf713ab8a72b807337ef7430f5bcce0ea2e940 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 20:46:50 +0000 Subject: [PATCH 71/84] direction.cc/testutil.cc: fix c++20 build error relating to inconsistent constinit in declaration and definition --- Firestore/core/src/core/direction.cc | 6 ++++-- Firestore/core/test/unit/testutil/testutil.cc | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Firestore/core/src/core/direction.cc b/Firestore/core/src/core/direction.cc index cf1e41cd216..8dde62e6d06 100644 --- a/Firestore/core/src/core/direction.cc +++ b/Firestore/core/src/core/direction.cc @@ -22,8 +22,10 @@ namespace firebase { namespace firestore { namespace core { -const Direction Direction::Ascending(Direction::AscendingModifier); -const Direction Direction::Descending(Direction::DescendingModifier); +ABSL_CONST_INIT const Direction + Direction::Ascending(Direction::AscendingModifier); +ABSL_CONST_INIT const Direction + Direction::Descending(Direction::DescendingModifier); std::string Direction::CanonicalId() const { return comparison_modifier_ == AscendingModifier ? "asc" : "desc"; diff --git a/Firestore/core/test/unit/testutil/testutil.cc b/Firestore/core/test/unit/testutil/testutil.cc index b0c46f66dfd..8b8c2d0650d 100644 --- a/Firestore/core/test/unit/testutil/testutil.cc +++ b/Firestore/core/test/unit/testutil/testutil.cc @@ -93,7 +93,7 @@ constexpr const char* kDeleteSentinel = ""; // the JDK (which is defined to normalize all NaNs to this value). This also // happens to be a common value for NAN in C++, but C++ does not require this // specific NaN value to be used, so we normalize. -const uint64_t kCanonicalNanBits = 0x7ff8000000000000ULL; +ABSL_CONST_INIT const uint64_t kCanonicalNanBits = 0x7ff8000000000000ULL; namespace details { From e4241e414d5a7e28eee003507a9cf318f307d0e0 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 21:09:04 +0000 Subject: [PATCH 72/84] cpplint.py: add back error categories: build/c++11, build/c++14, build/c++tr1 This fixes the bogus lint errors like this: Unknown NOLINT error category: build/c++11 [readability/nolint] [5] --- scripts/cpplint.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 43250d5628a..52010c3883c 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -269,6 +269,9 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', + 'build/c++11', + 'build/c++14', + 'build/c++tr1', 'build/deprecated', 'build/endif_comment', 'build/explicit_make_pair', From 167b4278cd2bbdb46c8fac63477736436ace2b13 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 10 Jan 2025 21:09:04 +0000 Subject: [PATCH 73/84] cpplint.py: add back error categories: build/c++11, build/c++14, build/c++tr1 This fixes the bogus lint errors like this: Unknown NOLINT error category: build/c++11 [readability/nolint] [5] --- scripts/cpplint.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 43250d5628a..52010c3883c 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -269,6 +269,9 @@ # here! cpplint_unittest.py should tell you if you forget to do this. _ERROR_CATEGORIES = [ 'build/class', + 'build/c++11', + 'build/c++14', + 'build/c++tr1', 'build/deprecated', 'build/endif_comment', 'build/explicit_make_pair', From a87d1590ec4fccff58186e3275829eda8e3e5946 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 11:00:01 -0500 Subject: [PATCH 74/84] git checkout --no-overlay origin/main . Reverting back to main in order to split up this pr --- .github/workflows/firestore.yml | 56 +-- Firestore/CHANGELOG.md | 1 - .../Firestore.xcodeproj/project.pbxproj | 30 -- Firestore/core/src/api/firestore.cc | 2 +- Firestore/core/src/core/composite_filter.cc | 19 +- Firestore/core/src/core/composite_filter.h | 3 +- Firestore/core/src/core/direction.cc | 6 +- Firestore/core/src/core/field_filter.cc | 11 +- Firestore/core/src/core/field_filter.h | 5 +- Firestore/core/src/core/filter.cc | 6 + Firestore/core/src/core/filter.h | 20 +- Firestore/core/src/core/query.cc | 77 ++-- Firestore/core/src/core/query.h | 31 +- .../core/src/local/lru_garbage_collector.cc | 2 +- .../core/src/util/thread_safe_memoizer.h | 191 ++------ .../unit/local/remote_document_cache_test.cc | 4 +- .../test/unit/remote/grpc_stream_tester.cc | 2 +- .../core/test/unit/remote/serializer_test.cc | 13 +- Firestore/core/test/unit/testutil/testutil.cc | 2 +- .../core/test/unit/testutil/utf8_testing.h | 69 --- .../test/unit/util/iterator_adaptors_test.cc | 7 - .../unit/util/thread_safe_memoizer_test.cc | 414 ++---------------- .../unit/util/thread_safe_memoizer_testing.cc | 104 ----- .../unit/util/thread_safe_memoizer_testing.h | 132 ------ .../util/thread_safe_memoizer_testing_test.cc | 196 --------- cmake/compiler_setup.cmake | 18 +- scripts/check.sh | 15 +- scripts/cpplint.py | 104 ++++- 28 files changed, 270 insertions(+), 1270 deletions(-) delete mode 100644 Firestore/core/test/unit/testutil/utf8_testing.h delete mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc delete mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing.h delete mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 8c3a9282c70..72280447aca 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -114,12 +114,7 @@ jobs: (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') strategy: matrix: - os: [ubuntu-latest] - cpp_version: [default, 17, 20] - compiler: [gcc, clang] - include: - - os: macos-14 - cpp_version: default + os: [macos-14, ubuntu-latest] env: MINT_PATH: ${{ github.workspace }}/mint @@ -132,9 +127,9 @@ jobs: uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache - key: firestore-ccache-${{ runner.os }}-cpp=${{ matrix.cpp_version }}-compiler=${{ matrix.compiler }}-${{ github.sha }} + key: firestore-ccache-${{ runner.os }}-${{ github.sha }} restore-keys: | - firestore-ccache-${{ runner.os }}-cpp=${{ matrix.cpp_version }}-compiler=${{ matrix.compiler }}- + firestore-ccache-${{ runner.os }}- - name: Cache Mint packages uses: actions/cache@v4 @@ -150,51 +145,6 @@ jobs: - name: Setup build run: scripts/install_prereqs.sh Firestore ${{ runner.os }} cmake - - name: Patch CMake files to use the custom C++ version - if: ${{ matrix.cpp_version != 'default' }} - run: | - readonly BEFORE=$(grep 'CMAKE_CXX_STANDARD\s\+[0-9]' cmake/compiler_setup.cmake) - echo "BEFORE=$BEFORE" - - sed -i -e 's/CMAKE_CXX_STANDARD\s\+[0-9]\+/CMAKE_CXX_STANDARD ${{ matrix.cpp_version }}/' cmake/compiler_setup.cmake - readonly AFTER=$(grep 'CMAKE_CXX_STANDARD\s\+[0-9]' cmake/compiler_setup.cmake) - echo "AFTER=$AFTER" - - if [[ $BEFORE == $AFTER ]] ; then - echo "ERROR: sed command did not actually change anything." >&2 - echo "Namely, the following line should have changed, but did not: $BEFORE" >&2 - echo "Make sure that the sed command is correct as it _should_ change the C++ version." >&2 - exit 1 - elif [[ $AFTER != *'${{ matrix.cpp_version }}'* ]] ; then - echo "ERROR: sed command did not set the correct C++ version." >&2 - echo "Namely, the following line should have contained ${{ matrix.cpp_version }} but it did not: $AFTER" >&2 - echo "Make sure that the sed command is correct as it _should_ result in the c++ version being incorporated." >&2 - exit 1 - else: - echo "C++ version changed successfully." - fi - - - name: Set CC and CXX environment variables - run: | - echo 'matrix.compiler=${{ matrix.compiler }}' - if [[ '${{ matrix.compiler }}' == 'gcc' ]] ; then - echo 'Setting CC and CXX for gcc' - echo "CC=gcc" >> $GITHUB_ENV - echo "CXX=g++" >> $GITHUB_ENV - gcc --version - g++ --version - elif [[ '${{ matrix.compiler }}' == 'clang' ]] ; then - echo 'Setting CC and CXX for clang' - echo "CC=clang" >> $GITHUB_ENV - echo "CXX=clang++" >> $GITHUB_ENV - clang --version - clang++ --version - else - echo 'Setting neither CC nor CXX' - cc --version - c++ --version - fi - - name: Build and test run: | export EXPERIMENTAL_MODE=true diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 2991355ae64..ad57793f111 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,5 +1,4 @@ # Unreleased -- [fixed] Fixed memory leak in `Query.whereField()`. (#13978) - [fixed] Fixed use-after-free bug when internally formatting strings. (#14306) # 11.6.0 diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 15abfd2adfb..7b6e8450bf1 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -259,7 +259,6 @@ 258B372CF33B7E7984BBA659 /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; 25C167BAA4284FC951206E1F /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; - 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 25FE27330996A59F31713A0C /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; }; 2618255E63631038B64DF3BB /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 2620644052E960310DADB298 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; @@ -401,7 +400,6 @@ 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; - 3D6AC48D6197E6539BBBD28F /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; 3DBB48F077C97200F32B51A0 /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 3DBBC644BE08B140BCC23BD5 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; @@ -434,7 +432,6 @@ 44A8B51C05538A8DACB85578 /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44C4244E42FFFB6E9D7F28BA /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44EAF3E6EAC0CC4EB2147D16 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; - 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 4562CDD90F5FF0491F07C5DA /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; 457171CE2510EEA46F7D8A30 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; @@ -455,7 +452,6 @@ 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; 47B8ED6737A24EF96B1ED318 /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; }; 4809D7ACAA9414E3192F04FF /* FIRGeoPointTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */; }; - 482D503CC826265FCEAB53DE /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 485CBA9F99771437BA1CB401 /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 48720B5768AFA2B2F3E14C04 /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 48926FF55484E996B474D32F /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = DD990FD89C165F4064B4F608 /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json */; }; @@ -787,7 +783,6 @@ 67B8C34BDF0FFD7532D7BE4F /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */; }; 67BC2B77C1CC47388E79D774 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; }; 67CF9FAA890307780731E1DA /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; - 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 6938575C8B5E6FE0D562547A /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; 6938ABD1891AD4B9FD5FE664 /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; }; 69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; }; @@ -878,7 +873,6 @@ 77C5703230DB77F0540D1F89 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */; }; 77D38E78F7CCB8504450A8FB /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; 77D3CF0BE43BC67B9A26B06D /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; - 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 784FCB02C76096DACCBA11F2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 78D99CDBB539B0AEE0029831 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 3841925AA60E13A027F565E6 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json */; }; 78E8DDDBE131F3DA9AF9F8B8 /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -997,7 +991,6 @@ 8C602DAD4E8296AB5EFB962A /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */; }; 8D0EF43F1B7B156550E65C20 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; - 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 8DBA8DC55722ED9D3A1BB2C9 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 1A7D48A017ECB54FD381D126 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json */; }; 8E103A426D6E650DC338F281 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; 8E41D53C77C30372840B0367 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 728F617782600536F2561463 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json */; }; @@ -1121,7 +1114,6 @@ A728A4D7FA17F9F3257E0002 /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8582DFD74E8060C7072104B /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json */; }; A7309DAD4A3B5334536ECA46 /* remote_event_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 584AE2C37A55B408541A6FF3 /* remote_event_test.cc */; }; A7399FB3BEC50BBFF08EC9BA /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; - A7669E72BCED7FBADA4B1314 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; A80D38096052F928B17E1504 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; A833A216988ADFD4876763CD /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; A841EEB5A94A271523EAE459 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = A5D9044B72061CAF284BC9E4 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json */; }; @@ -1276,7 +1268,6 @@ BC8DFBCB023DBD914E27AA7D /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; BCA720A0F54D23654F806323 /* ConditionalConformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3228F51DCDC2E90D5C58F97 /* ConditionalConformanceTests.swift */; }; BCAC9F7A865BD2320A4D8752 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; - BD0882A40BD8AE042629C179 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; BD3A421C9E40C57D25697E75 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BD051DBE754950FEAC7A446 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json */; }; BD6CC8614970A3D7D2CF0D49 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; BDD2D1812BAD962E3C81A53F /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; @@ -1293,7 +1284,6 @@ BFEAC4151D3AA8CE1F92CC2D /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; C02A969BF4BB63ABCB531B4B /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */; }; C06E54352661FCFB91968640 /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; - C099AEC05D44976755BA32A2 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; C09BDBA73261578F9DA74CEE /* firebase_auth_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = F869D85E900E5AF6CD02E2FC /* firebase_auth_credentials_provider_test.mm */; }; C0AD8DB5A84CAAEE36230899 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; C0EFC5FB79517679C377C252 /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; @@ -1368,7 +1358,6 @@ CE2962775B42BDEEE8108567 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; CE411D4B70353823DE63C0D5 /* bundle_loader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A853C81A6A5A51C9D0389EDA /* bundle_loader_test.cc */; }; CEA91CE103B42533C54DBAD6 /* memory_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */; }; - CF18D52A88F4F6F62C5495EF /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; CF1FB026CCB901F92B4B2C73 /* watch_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2D7472BC70C024D736FF74D9 /* watch_change_test.cc */; }; CF5DE1ED21DD0A9783383A35 /* CodableIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124C932B22C1642C00CA8C2D /* CodableIntegrationTests.swift */; }; CFA4A635ECD105D2044B3692 /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3355BE9391CC4857AF0BDAE3 /* DatabaseTests.swift */; }; @@ -1420,7 +1409,6 @@ D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; - D928302820891CCCAD0437DD /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; D94A1862B8FB778225DB54A1 /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; D98430EA4FAA357D855FA50F /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; @@ -1737,7 +1725,6 @@ 214877F52A705012D6720CA0 /* object_value_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = object_value_test.cc; sourceTree = ""; }; 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_target_cache_test.cc; sourceTree = ""; }; - 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = thread_safe_memoizer_testing.h; sourceTree = ""; }; 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = lru_garbage_collector_test.cc; sourceTree = ""; }; 28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = logic_utils_test.cc; sourceTree = ""; }; 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_document_overlay_cache_test.cc; sourceTree = ""; }; @@ -1945,7 +1932,6 @@ 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = ""; }; 6A7A30A2DB3367E08939E789 /* bloom_filter.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = bloom_filter.pb.h; sourceTree = ""; }; 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.release.xcconfig"; sourceTree = ""; }; - 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_testing.cc; sourceTree = ""; }; 6E8302DE210222ED003E1EA3 /* FSTFuzzTestFieldPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTFuzzTestFieldPath.h; sourceTree = ""; }; 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestFieldPath.mm; sourceTree = ""; }; 6EA39FDD20FE820E008D461F /* FSTFuzzTestSerializer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestSerializer.mm; sourceTree = ""; }; @@ -2123,7 +2109,6 @@ E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = ""; }; E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_target_cache_test.cc; sourceTree = ""; }; - EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_testing_test.cc; sourceTree = ""; }; ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EF3A65472C66B9560041EE69 /* FIRVectorValueTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRVectorValueTests.mm; sourceTree = ""; }; EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRAggregateTests.mm; sourceTree = ""; }; @@ -2427,9 +2412,6 @@ 899FC22684B0F7BEEAE13527 /* task_test.cc */, A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */, 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */, - 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */, - 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */, - EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */, B68B1E002213A764008977EF /* to_string_apple_test.mm */, B696858D2214B53900271095 /* to_string_test.cc */, ); @@ -4345,8 +4327,6 @@ 9B2C6A48A4DBD36080932B4E /* testing_hooks_test.cc in Sources */, 32A95242C56A1A230231DB6A /* testutil.cc in Sources */, 51018EA27CF914DD1CC79CB3 /* thread_safe_memoizer_test.cc in Sources */, - 482D503CC826265FCEAB53DE /* thread_safe_memoizer_testing.cc in Sources */, - 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB78229DECDE000FB92F /* time_testing.cc in Sources */, ACC9369843F5ED3BD2284078 /* timestamp_test.cc in Sources */, 2AAEABFD550255271E3BAC91 /* to_string_apple_test.mm in Sources */, @@ -4568,8 +4548,6 @@ 24B75C63BDCD5551B2F69901 /* testing_hooks_test.cc in Sources */, 8388418F43042605FB9BFB92 /* testutil.cc in Sources */, 5BB33F0BC7960D26062B07D3 /* thread_safe_memoizer_test.cc in Sources */, - 3D6AC48D6197E6539BBBD28F /* thread_safe_memoizer_testing.cc in Sources */, - 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB79229DECDE000FB92F /* time_testing.cc in Sources */, 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */, 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */, @@ -4815,8 +4793,6 @@ D0DA42DC66C4FE508A63B269 /* testing_hooks_test.cc in Sources */, 409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */, B31B5E0D4EA72C5916CC71F5 /* thread_safe_memoizer_test.cc in Sources */, - 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */, - 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */, 6300709ECDE8E0B5A8645F8D /* time_testing.cc in Sources */, 0CEE93636BA4852D3C5EC428 /* timestamp_test.cc in Sources */, 95DCD082374F871A86EF905F /* to_string_apple_test.mm in Sources */, @@ -5062,8 +5038,6 @@ F6738D3B72352BBEFB87172C /* testing_hooks_test.cc in Sources */, A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */, 09B83B26E47B6F6668DF54B8 /* thread_safe_memoizer_test.cc in Sources */, - CF18D52A88F4F6F62C5495EF /* thread_safe_memoizer_testing.cc in Sources */, - A7669E72BCED7FBADA4B1314 /* thread_safe_memoizer_testing_test.cc in Sources */, A25FF76DEF542E01A2DF3B0E /* time_testing.cc in Sources */, 1E42CD0F60EB22A5D0C86D1F /* timestamp_test.cc in Sources */, F9705E595FC3818F13F6375A /* to_string_apple_test.mm in Sources */, @@ -5295,8 +5269,6 @@ F184E5367DF3CA158EDE8532 /* testing_hooks_test.cc in Sources */, 54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */, 20A93AC59CD5A7AC41F10412 /* thread_safe_memoizer_test.cc in Sources */, - 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */, - BD0882A40BD8AE042629C179 /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB77229DECDE000FB92F /* time_testing.cc in Sources */, ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */, B68B1E012213A765008977EF /* to_string_apple_test.mm in Sources */, @@ -5561,8 +5533,6 @@ 5360D52DCAD1069B1E4B0B9D /* testing_hooks_test.cc in Sources */, CA989C0E6020C372A62B7062 /* testutil.cc in Sources */, 6DFD49CCE2281CE243FEBB63 /* thread_safe_memoizer_test.cc in Sources */, - D928302820891CCCAD0437DD /* thread_safe_memoizer_testing.cc in Sources */, - C099AEC05D44976755BA32A2 /* thread_safe_memoizer_testing_test.cc in Sources */, 2D220B9ABFA36CD7AC43D0A7 /* time_testing.cc in Sources */, D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */, 60260A06871DCB1A5F3448D3 /* to_string_apple_test.mm in Sources */, diff --git a/Firestore/core/src/api/firestore.cc b/Firestore/core/src/api/firestore.cc index 30ddf96388f..70cb975cc71 100644 --- a/Firestore/core/src/api/firestore.cc +++ b/Firestore/core/src/api/firestore.cc @@ -193,7 +193,7 @@ void Firestore::WaitForPendingWrites(util::StatusCallback callback) { void Firestore::ClearPersistence(util::StatusCallback callback) { worker_queue()->EnqueueEvenWhileRestricted([this, callback] { - auto MaybeCallback = [this, callback](Status status) { + auto MaybeCallback = [=](Status status) { if (callback) { user_executor_->Execute([=] { callback(status); }); } diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 02186989330..7bbb81abc57 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -17,7 +17,6 @@ #include "Firestore/core/src/core/composite_filter.h" #include -#include #include #include "Firestore/core/src/core/field_filter.h" @@ -142,14 +141,16 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( return nullptr; } -std::shared_ptr> -CompositeFilter::Rep::CalculateFlattenedFilters() const { - auto flattened_filters = std::make_shared>(); - for (const auto& filter : filters()) - std::copy(filter.GetFlattenedFilters().begin(), - filter.GetFlattenedFilters().end(), - std::back_inserter(*flattened_filters)); - return flattened_filters; +const std::vector& CompositeFilter::Rep::GetFlattenedFilters() + const { + return memoized_flattened_filters_->memoize([&]() { + std::vector flattened_filters; + for (const auto& filter : filters()) + std::copy(filter.GetFlattenedFilters().begin(), + filter.GetFlattenedFilters().end(), + std::back_inserter(flattened_filters)); + return flattened_filters; + }); } } // namespace core diff --git a/Firestore/core/src/core/composite_filter.h b/Firestore/core/src/core/composite_filter.h index 2858271a358..24671d3e44e 100644 --- a/Firestore/core/src/core/composite_filter.h +++ b/Firestore/core/src/core/composite_filter.h @@ -138,8 +138,7 @@ class CompositeFilter : public Filter { return filters_.empty(); } - std::shared_ptr> CalculateFlattenedFilters() - const override; + const std::vector& GetFlattenedFilters() const override; std::vector GetFilters() const override { return filters(); diff --git a/Firestore/core/src/core/direction.cc b/Firestore/core/src/core/direction.cc index 8dde62e6d06..cf1e41cd216 100644 --- a/Firestore/core/src/core/direction.cc +++ b/Firestore/core/src/core/direction.cc @@ -22,10 +22,8 @@ namespace firebase { namespace firestore { namespace core { -ABSL_CONST_INIT const Direction - Direction::Ascending(Direction::AscendingModifier); -ABSL_CONST_INIT const Direction - Direction::Descending(Direction::DescendingModifier); +const Direction Direction::Ascending(Direction::AscendingModifier); +const Direction Direction::Descending(Direction::DescendingModifier); std::string Direction::CanonicalId() const { return comparison_modifier_ == AscendingModifier ? "asc" : "desc"; diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index 2867e3ba8ba..b68956c8a52 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -16,7 +16,6 @@ #include "Firestore/core/src/core/field_filter.h" -#include #include #include "Firestore/core/src/core/array_contains_any_filter.h" @@ -123,12 +122,12 @@ FieldFilter::FieldFilter(std::shared_ptr rep) : Filter(std::move(rep)) { } -std::shared_ptr> -FieldFilter::Rep::CalculateFlattenedFilters() const { +const std::vector& FieldFilter::Rep::GetFlattenedFilters() const { // This is already a field filter, so we return a vector of size one. - auto filters = std::make_shared>(); - filters->push_back(FieldFilter(std::make_shared(*this))); - return filters; + return memoized_flattened_filters_->memoize([&]() { + return std::vector{ + FieldFilter(std::make_shared(*this))}; + }); } std::vector FieldFilter::Rep::GetFilters() const { diff --git a/Firestore/core/src/core/field_filter.h b/Firestore/core/src/core/field_filter.h index 2f03254e1a1..48219f222f2 100644 --- a/Firestore/core/src/core/field_filter.h +++ b/Firestore/core/src/core/field_filter.h @@ -117,6 +117,8 @@ class FieldFilter : public Filter { return false; } + const std::vector& GetFlattenedFilters() const override; + std::vector GetFilters() const override; protected: @@ -138,9 +140,6 @@ class FieldFilter : public Filter { bool MatchesComparison(util::ComparisonResult comparison) const; - std::shared_ptr> CalculateFlattenedFilters() - const override; - private: friend class FieldFilter; diff --git a/Firestore/core/src/core/filter.cc b/Firestore/core/src/core/filter.cc index 853c15b31ba..a77ccc55e34 100644 --- a/Firestore/core/src/core/filter.cc +++ b/Firestore/core/src/core/filter.cc @@ -35,6 +35,12 @@ std::ostream& operator<<(std::ostream& os, const Filter& filter) { return os << filter.ToString(); } +Filter::Rep::Rep() + : memoized_flattened_filters_( + std::make_shared< + util::ThreadSafeMemoizer>>()) { +} + } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index bab79599cc6..ec20120daf6 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -17,7 +17,6 @@ #ifndef FIRESTORE_CORE_SRC_CORE_FILTER_H_ #define FIRESTORE_CORE_SRC_CORE_FILTER_H_ -#include #include #include #include @@ -115,7 +114,7 @@ class Filter { protected: class Rep { public: - Rep() = default; + Rep(); virtual ~Rep() = default; @@ -148,23 +147,20 @@ class Filter { virtual bool IsEmpty() const = 0; - virtual const std::vector& GetFlattenedFilters() const { - const auto func = std::bind(&Rep::CalculateFlattenedFilters, this); - return memoized_flattened_filters_.value(func); - } + virtual const std::vector& GetFlattenedFilters() const = 0; virtual std::vector GetFilters() const = 0; - protected: - virtual std::shared_ptr> - CalculateFlattenedFilters() const = 0; - - private: /** * Memoized list of all field filters that can be found by * traversing the tree of filters contained in this composite filter. + * + * Use a `std::shared_ptr` rather than using + * `ThreadSafeMemoizer` directly so that this class is copyable + * (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag` + * member variable, which is not copyable). */ - mutable util::ThreadSafeMemoizer> + mutable std::shared_ptr>> memoized_flattened_filters_; }; diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 1c8394748d9..5b70ebc322f 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -17,7 +17,6 @@ #include "Firestore/core/src/core/query.h" #include -#include #include #include "Firestore/core/src/core/bound.h" @@ -92,42 +91,44 @@ absl::optional Query::FindOpInsideFilters( return absl::nullopt; } -std::shared_ptr> Query::CalculateNormalizedOrderBys() - const { - // Any explicit order by fields should be added as is. - auto result = std::make_shared>(explicit_order_bys_); - std::set fieldsNormalized; - for (const OrderBy& order_by : explicit_order_bys_) { - fieldsNormalized.insert(order_by.field()); - } +const std::vector& Query::normalized_order_bys() const { + return memoized_normalized_order_bys_->memoize([&]() { + // Any explicit order by fields should be added as is. + std::vector result = explicit_order_bys_; + std::set fieldsNormalized; + for (const OrderBy& order_by : explicit_order_bys_) { + fieldsNormalized.insert(order_by.field()); + } - // The order of the implicit ordering always matches the last explicit order - // by. - Direction last_direction = explicit_order_bys_.empty() - ? Direction::Ascending - : explicit_order_bys_.back().direction(); - - // Any inequality fields not explicitly ordered should be implicitly ordered - // in a lexicographical order. When there are multiple inequality filters on - // the same field, the field should be added only once. Note: - // `std::set` sorts the key field before other fields. - // However, we want the key field to be sorted last. - const std::set inequality_fields = InequalityFilterFields(); - - for (const model::FieldPath& field : inequality_fields) { - if (fieldsNormalized.find(field) == fieldsNormalized.end() && - !field.IsKeyFieldPath()) { - result->push_back(OrderBy(field, last_direction)); + // The order of the implicit ordering always matches the last explicit order + // by. + Direction last_direction = explicit_order_bys_.empty() + ? Direction::Ascending + : explicit_order_bys_.back().direction(); + + // Any inequality fields not explicitly ordered should be implicitly ordered + // in a lexicographical order. When there are multiple inequality filters on + // the same field, the field should be added only once. Note: + // `std::set` sorts the key field before other fields. + // However, we want the key field to be sorted last. + const std::set inequality_fields = + InequalityFilterFields(); + + for (const model::FieldPath& field : inequality_fields) { + if (fieldsNormalized.find(field) == fieldsNormalized.end() && + !field.IsKeyFieldPath()) { + result.push_back(OrderBy(field, last_direction)); + } } - } - // Add the document key field to the last if it is not explicitly ordered. - if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == - fieldsNormalized.end()) { - result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); - } + // Add the document key field to the last if it is not explicitly ordered. + if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == + fieldsNormalized.end()) { + result.push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); + } - return result; + return result; + }); } LimitType Query::limit_type() const { @@ -295,12 +296,14 @@ std::string Query::ToString() const { return absl::StrCat("Query(canonical_id=", CanonicalId(), ")"); } -std::shared_ptr Query::CalculateTarget() const { - return std::make_shared(ToTarget(normalized_order_bys())); +const Target& Query::ToTarget() const& { + return memoized_target_->memoize( + [&]() { return ToTarget(normalized_order_bys()); }); } -std::shared_ptr Query::CalculateAggregateTarget() const { - return std::make_shared(ToTarget(explicit_order_bys_)); +const Target& Query::ToAggregateTarget() const& { + return memoized_aggregate_target_->memoize( + [&]() { return ToTarget(explicit_order_bys_); }); } Target Query::ToTarget(const std::vector& order_bys) const { diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index d2b7b3247ff..23351a4de56 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -17,7 +17,6 @@ #ifndef FIRESTORE_CORE_SRC_CORE_QUERY_H_ #define FIRESTORE_CORE_SRC_CORE_QUERY_H_ -#include #include #include #include @@ -149,10 +148,7 @@ class Query { * This might include additional sort orders added implicitly to match the * backend behavior. */ - const std::vector& normalized_order_bys() const { - const auto func = std::bind(&Query::CalculateNormalizedOrderBys, this); - return memoized_normalized_order_bys_.value(func); - } + const std::vector& normalized_order_bys() const; bool has_limit() const { return limit_ != Target::kNoLimit; @@ -250,10 +246,7 @@ class Query { * Returns a `Target` instance this query will be mapped to in backend * and local store. */ - const Target& ToTarget() const& { - const auto func = std::bind(&Query::CalculateTarget, this); - return memoized_target_.value(func); - } + const Target& ToTarget() const&; /** * Returns a `Target` instance this query will be mapped to in backend @@ -261,10 +254,7 @@ class Query { * for non-aggregate queries, aggregate query targets do not contain * normalized order-bys, they only contain explicit order-bys. */ - const Target& ToAggregateTarget() const& { - const auto func = std::bind(&Query::CalculateAggregateTarget, this); - return memoized_aggregate_target_.value(func); - } + const Target& ToAggregateTarget() const&; friend std::ostream& operator<<(std::ostream& os, const Query& query); @@ -305,19 +295,20 @@ class Query { // member variable, which is not copyable). // The memoized list of sort orders. - std::shared_ptr> CalculateNormalizedOrderBys() const; - mutable util::ThreadSafeMemoizer> - memoized_normalized_order_bys_; + mutable std::shared_ptr>> + memoized_normalized_order_bys_{ + std::make_shared>>()}; // The corresponding Target of this Query instance. - std::shared_ptr CalculateTarget() const; - mutable util::ThreadSafeMemoizer memoized_target_; + mutable std::shared_ptr> memoized_target_{ + std::make_shared>()}; // The corresponding aggregate Target of this Query instance. Unlike targets // for non-aggregate queries, aggregate query targets do not contain // normalized order-bys, they only contain explicit order-bys. - std::shared_ptr CalculateAggregateTarget() const; - mutable util::ThreadSafeMemoizer memoized_aggregate_target_; + mutable std::shared_ptr> + memoized_aggregate_target_{ + std::make_shared>()}; }; bool operator==(const Query& lhs, const Query& rhs); diff --git a/Firestore/core/src/local/lru_garbage_collector.cc b/Firestore/core/src/local/lru_garbage_collector.cc index b212dd3616e..03f191f8609 100644 --- a/Firestore/core/src/local/lru_garbage_collector.cc +++ b/Firestore/core/src/local/lru_garbage_collector.cc @@ -89,7 +89,7 @@ class RollingSequenceNumberBuffer { } // namespace -ABSL_CONST_INIT const ListenSequenceNumber kListenSequenceNumberInvalid = -1; +const ListenSequenceNumber kListenSequenceNumberInvalid = -1; LruParams LruParams::Default() { return LruParams{100 * 1024 * 1024, 10, 1000}; diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 2e0e928b9f9..5bc6fc1c529 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,182 +18,63 @@ #define FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ #include -#include -#include +#include // NOLINT(build/c++11) +#include namespace firebase { namespace firestore { namespace util { -// Put the C++11 and C++20 implementations into different inline namespaces so -// that if, by chance, parts of the code compile with different values of -// `__cpp_lib_atomic_shared_ptr` this will not result in an ODR-violation -// (at least on account of just the differing `__cpp_lib_atomic_shared_ptr`). -// -// TODO(c++20): Remove the inline namespaces once the other #ifdef checks for -// __cpp_lib_atomic_shared_ptr are removed. -#ifdef __cpp_lib_atomic_shared_ptr -inline namespace cpp20_atomic_shared_ptr { -#else -inline namespace cpp11_atomic_free_functions { -#endif - /** * Stores a memoized value in a manner that is safe to be shared between * multiple threads. + * + * TODO(b/299933587) Make `ThreadSafeMemoizer` copyable and moveable. */ template class ThreadSafeMemoizer { public: - /** - * Creates a new ThreadSafeMemoizer with no memoized value. - */ - ThreadSafeMemoizer() { - memoize_clear(memoized_); + ThreadSafeMemoizer() = default; + + ~ThreadSafeMemoizer() { + // Call `std::call_once` in order to synchronize with the "active" + // invocation of `memoize()`. Without this synchronization, there is a data + // race between this destructor, which "reads" `memoized_value_` to destroy + // it, and the write to `memoized_value_` done by the "active" invocation of + // `memoize()`. + std::call_once(once_, [&]() {}); } - /** - * Copy constructor: creates a new ThreadSafeMemoizer object with the same - * memoized value as the ThreadSafeMemoizer object referred to by the given - * reference. - * - * The runtime performance of this function is O(1). - */ - ThreadSafeMemoizer(const ThreadSafeMemoizer& other) { - operator=(other); - } - - /** - * Copy assignment operator: replaces this object's memoized value with the - * memoized value of the ThreadSafeMemoizer object referred to by the given - * reference. - * - * The runtime performance of this function is O(1). - */ - ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { - memoize_store(memoized_, memoize_load(other.memoized_)); - return *this; - } + // This class cannot be copied or moved, because it has `std::once_flag` + // member. + ThreadSafeMemoizer(const ThreadSafeMemoizer&) = delete; + ThreadSafeMemoizer(ThreadSafeMemoizer&&) = delete; + ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer&) = delete; + ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&&) = delete; /** - * Move constructor: creates a new ThreadSafeMemoizer object with the same - * memoized value as the ThreadSafeMemoizer object referred to by the given - * reference, also clearing its memoized value. + * Memoize a value. * - * The runtime performance of this function is O(1). + * The std::function object specified by the first invocation of this + * function (the "active" invocation) will be invoked synchronously. + * None of the std::function objects specified by the subsequent + * invocations of this function (the "passive" invocations) will be + * invoked. All invocations, both "active" and "passive", will return a + * reference to the std::vector created by copying the return value from + * the std::function specified by the "active" invocation. It is, + * therefore, the "active" invocation's job to return the std::vector + * to memoize. */ - ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept { - operator=(std::move(other)); - } - - /** - * Move assignment operator: replaces this object's memoized value with the - * memoized value of the ThreadSafeMemoizer object referred to by the given - * reference, also clearing its memoized value. - * - * The runtime performance of this function is O(1). - */ - ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { - memoize_store(memoized_, memoize_load(other.memoized_)); - memoize_clear(other.memoized_); - return *this; - } - - /** - * Return the memoized value, calculating it with the given function if - * needed. - * - * If this object _does_ have a memoized value then this function simply - * returns a reference to it and does _not_ call the given function. - * - * On the other hand, if this object does _not_ have a memoized value then - * the given function is called to calculate the value to memoize. The value - * returned by the function is stored internally as the "memoized value" and - * then returned. - * - * The given function *must* be idempotent because it _may_ be called more - * than once due to the semantics of "weak" compare-and-exchange. No reference - * to the given function is retained by this object. The given function will - * be called synchronously by this function, if it is called at all. - * - * This function is thread-safe and may be called concurrently by multiple - * threads. - * - * The returned reference should only be considered "valid" as long as this - * ThreadSafeMemoizer instance is alive. - */ - const T& value(const std::function()>& func) { - std::shared_ptr old_memoized = memoize_load(memoized_); - - while (true) { - if (old_memoized) { - return *old_memoized; - } - - std::shared_ptr new_memoized = func(); - - if (memoize_compare_exchange(memoized_, old_memoized, new_memoized)) { - return *new_memoized; - } - } + const T& memoize(std::function func) { + std::call_once(once_, [&]() { memoized_value_ = func(); }); + return memoized_value_; } private: - // TODO(c++20): Remove the #ifdef checks for __cpp_lib_atomic_shared_ptr and - // delete all code that is compiled out when __cpp_lib_atomic_shared_ptr is - // defined. -#ifdef __cpp_lib_atomic_shared_ptr - std::atomic> memoized_; - - static void memoize_store(std::atomic>& memoized, - const std::shared_ptr& value) { - memoized.store(value); - } - - static void memoize_clear(std::atomic>& memoized) { - memoized.store(std::shared_ptr()); - } - - static std::shared_ptr memoize_load( - const std::atomic>& memoized) { - return memoized.load(); - } - - static bool memoize_compare_exchange( - std::atomic>& memoized, - std::shared_ptr& expected, - const std::shared_ptr& desired) { - return memoized.compare_exchange_weak(expected, desired); - } - -#else // #ifdef __cpp_lib_atomic_shared_ptr - // NOTE: Always use the std::atomic_XXX() functions to access this shared_ptr - // to ensure thread safety. - // See https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic. - std::shared_ptr memoized_; - - static void memoize_store(std::shared_ptr& memoized, - const std::shared_ptr& value) { - std::atomic_store(&memoized, value); - } - - static void memoize_clear(std::shared_ptr& memoized) { - std::atomic_store(&memoized, std::shared_ptr()); - } - - static std::shared_ptr memoize_load(const std::shared_ptr& memoized) { - return std::atomic_load(&memoized); - } - - static bool memoize_compare_exchange(std::shared_ptr& memoized, - std::shared_ptr& expected, - const std::shared_ptr& desired) { - return std::atomic_compare_exchange_weak(&memoized, &expected, desired); - } - -#endif // #ifdef __cpp_lib_atomic_shared_ptr + std::once_flag once_; + T memoized_value_; }; -} // namespace cpp20_atomic_shared_ptr/cpp11_atomic_free_functions + } // namespace util } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.cc b/Firestore/core/test/unit/local/remote_document_cache_test.cc index e0bd10917d9..64918b79223 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.cc +++ b/Firestore/core/test/unit/local/remote_document_cache_test.cc @@ -122,7 +122,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadADocument) { } TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { - persistence_->Run("test_set_and_read_several_documents", [this] { + persistence_->Run("test_set_and_read_several_documents", [=] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), @@ -136,7 +136,7 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocumentsIncludingMissingDocument) { persistence_->Run( - "test_set_and_read_several_documents_including_missing_document", [this] { + "test_set_and_read_several_documents_including_missing_document", [=] { std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), diff --git a/Firestore/core/test/unit/remote/grpc_stream_tester.cc b/Firestore/core/test/unit/remote/grpc_stream_tester.cc index eed2275e21f..f59a4e8eec1 100644 --- a/Firestore/core/test/unit/remote/grpc_stream_tester.cc +++ b/Firestore/core/test/unit/remote/grpc_stream_tester.cc @@ -187,7 +187,7 @@ std::future FakeGrpcQueue::KeepPolling( const CompletionCallback& callback) { current_promise_ = {}; - dedicated_executor_->Execute([this, callback] { + dedicated_executor_->Execute([=] { bool done = false; while (!done) { auto* completion = ExtractCompletion(); diff --git a/Firestore/core/test/unit/remote/serializer_test.cc b/Firestore/core/test/unit/remote/serializer_test.cc index 1a6c14060ab..14b08b1e13f 100644 --- a/Firestore/core/test/unit/remote/serializer_test.cc +++ b/Firestore/core/test/unit/remote/serializer_test.cc @@ -61,7 +61,6 @@ #include "Firestore/core/test/unit/nanopb/nanopb_testing.h" #include "Firestore/core/test/unit/testutil/status_testing.h" #include "Firestore/core/test/unit/testutil/testutil.h" -#include "Firestore/core/test/unit/testutil/utf8_testing.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "google/protobuf/stubs/common.h" @@ -122,7 +121,6 @@ using testutil::OrderBy; using testutil::OrFilters; using testutil::Query; using testutil::Ref; -using testutil::StringFromU8String; using testutil::Value; using testutil::Version; using util::Status; @@ -662,22 +660,19 @@ TEST_F(SerializerTest, EncodesString) { "", "a", "abc def", - StringFromU8String(u8"æ"), + u8"æ", // Note: Each one of the three embedded universal character names // (\u-escaped) maps to three chars, so the total length of the string // literal is 10 (ignoring the terminating null), and the resulting string // literal is the same as '\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf'". The // size of 10 must be added, or else std::string will see the \0 at the // start and assume that's the end of the string. - StringFromU8String(u8"\0\ud7ff\ue000\uffff", 10), + {u8"\0\ud7ff\ue000\uffff", 10}, {"\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf", 10}, - StringFromU8String(u8"(╯°□°)╯︵ ┻━┻"), + u8"(╯°□°)╯︵ ┻━┻", }; - for (decltype(cases.size()) i = 0; i < cases.size(); ++i) { - const std::string& string_value = cases[i]; - SCOPED_TRACE("iteration " + std::to_string(i) + - ": string_value=" + string_value); + for (const std::string& string_value : cases) { Message model = Value(string_value); ExpectRoundTrip(model, ValueProto(string_value), TypeOrder::kString); } diff --git a/Firestore/core/test/unit/testutil/testutil.cc b/Firestore/core/test/unit/testutil/testutil.cc index 8b8c2d0650d..b0c46f66dfd 100644 --- a/Firestore/core/test/unit/testutil/testutil.cc +++ b/Firestore/core/test/unit/testutil/testutil.cc @@ -93,7 +93,7 @@ constexpr const char* kDeleteSentinel = ""; // the JDK (which is defined to normalize all NaNs to this value). This also // happens to be a common value for NAN in C++, but C++ does not require this // specific NaN value to be used, so we normalize. -ABSL_CONST_INIT const uint64_t kCanonicalNanBits = 0x7ff8000000000000ULL; +const uint64_t kCanonicalNanBits = 0x7ff8000000000000ULL; namespace details { diff --git a/Firestore/core/test/unit/testutil/utf8_testing.h b/Firestore/core/test/unit/testutil/utf8_testing.h deleted file mode 100644 index 07d4b1a6dfc..00000000000 --- a/Firestore/core/test/unit/testutil/utf8_testing.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_TEST_UNIT_TESTUTIL_UTF8_TESTING_H_ -#define FIRESTORE_CORE_TEST_UNIT_TESTUTIL_UTF8_TESTING_H_ - -#include - -namespace firebase { -namespace firestore { -namespace testutil { - -// TODO(c++20): Remove the #if check below and delete the #else block. -#if __cplusplus >= 202002L - -// Creates a std::string whose contents are the bytes of the given null -// terminated string. -// e.g. std::string s = StringFromU8String(u8"foobar"); -constexpr std::string StringFromU8String(const char8_t* s) { - const std::u8string u8s(s); - return std::string(u8s.begin(), u8s.end()); -} - -// Creates a std::string whose contents are the first count bytes of the given -// null terminated string. -// e.g. std::string s = StringFromU8String(u8"foobar", 4); -constexpr std::string StringFromU8String(const char8_t* s, - std::string::size_type count) { - const std::u8string u8s(s, count); - return std::string(u8s.begin(), u8s.end()); -} - -#else // __cplusplus >= 202002L - -// Creates a std::string whose contents are the bytes of the given null -// terminated string. -// e.g. std::string s = StringFromU8String(u8"foobar"); -inline std::string StringFromU8String(const char* s) { - return std::string(s); -} - -// Creates a std::string whose contents are the first count bytes of the given -// null terminated string. -// e.g. std::string s = StringFromU8String(u8"foobar", 4); -inline std::string StringFromU8String(const char* s, - std::string::size_type count) { - return std::string(s, count); -} - -#endif // __cplusplus >= 202002L - -} // namespace testutil -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_TEST_UNIT_TESTUTIL_UTF8_TESTING_H_ diff --git a/Firestore/core/test/unit/util/iterator_adaptors_test.cc b/Firestore/core/test/unit/util/iterator_adaptors_test.cc index 1d034ffbbd6..6774d3b260f 100644 --- a/Firestore/core/test/unit/util/iterator_adaptors_test.cc +++ b/Firestore/core/test/unit/util/iterator_adaptors_test.cc @@ -28,7 +28,6 @@ #include #include -#include "Firestore/core/src/util/warnings.h" #include "absl/base/macros.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -82,11 +81,8 @@ class IteratorAdaptorTest : public testing::Test { virtual void TearDown() { } - // Suppress C++20 warning: struct std::iterator is deprecated - SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() template class InlineStorageIter : public std::iterator { - SUPPRESS_END() public: T* operator->() const { return get(); @@ -571,11 +567,8 @@ TEST_F(IteratorAdaptorTest, IteratorPtrHasRandomAccessMethods) { EXPECT_EQ(88, value2); } -// Suppress C++20 warning: struct std::iterator is deprecated -SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() class MyInputIterator : public std::iterator { - SUPPRESS_END() public: explicit MyInputIterator(int* x) : x_(x) { } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index d7d40b8eb8e..767fa7cb318 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,98 +16,40 @@ #include "Firestore/core/src/util/thread_safe_memoizer.h" -#include -#include -#include -#include - -#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" -#include "gmock/gmock.h" +#include // NOLINT(build/c++11) #include "gtest/gtest.h" -namespace { - -using namespace std::literals::string_literals; -using firebase::firestore::testing::CountDownLatch; -using firebase::firestore::testing::CountingFunc; -using firebase::firestore::testing::FST_RE_DIGIT; -using firebase::firestore::testing::max_practical_parallel_threads_for_testing; -using firebase::firestore::testing::SetOnDestructor; -using firebase::firestore::util::ThreadSafeMemoizer; -using testing::MatchesRegex; - -TEST(ThreadSafeMemoizerTest, DefaultConstructor) { - ThreadSafeMemoizer memoizer; - auto func = [] { return std::make_shared(42); }; - ASSERT_EQ(memoizer.value(func), 42); -} - -TEST(ThreadSafeMemoizerTest, Value_ShouldReturnComputedValueOnFirstInvocation) { - ThreadSafeMemoizer memoizer; - CountingFunc counter("rztsygzy5z"); - ASSERT_EQ(memoizer.value(counter.func()), "rztsygzy5z"); -} +namespace firebase { +namespace firestore { +namespace util { -TEST(ThreadSafeMemoizerTest, - Value_ShouldReturnMemoizedValueOnSubsequentInvocations) { - ThreadSafeMemoizer memoizer; - CountingFunc counter("tfj6v4kdxn_%s"); - auto func = counter.func(); +TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { + std::atomic global_int{77}; - const auto expected = memoizer.value(func); - // Do not hardcode "tfj6v4kdxn_0" as the expected value because - // ThreadSafeMemoizer.value() documents that it _may_ call the given function - // multiple times. - ASSERT_THAT(memoizer.value(func), - MatchesRegex("tfj6v4kdxn_"s + FST_RE_DIGIT + "+")); + auto expensive_lambda = [&]() { + // Simulate an expensive operation + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // If the lambda gets executed multiple times, threads will see incremented + // `global_int`. + global_int++; + return global_int.load(); + }; - for (int i = 0; i < 100; i++) { - SCOPED_TRACE("iteration i=" + std::to_string(i)); - ASSERT_EQ(memoizer.value(func), expected); - } -} + const int num_threads = 5; + const int expected_result = 78; -TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { - ThreadSafeMemoizer memoizer; - CountingFunc counter; - auto func = counter.func(); - memoizer.value(func); - // Do not hardcode 1 as the expected invocation count because - // ThreadSafeMemoizer.value() documents that it _may_ call the given function - // multiple times. - const auto expected_invocation_count = counter.invocation_count(); - for (int i = 0; i < 100; i++) { - memoizer.value(func); - } - EXPECT_EQ(counter.invocation_count(), expected_invocation_count); -} + // Create a thread safe memoizer and multiple threads. + util::ThreadSafeMemoizer memoized_result; + std::vector threads; -TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { - ThreadSafeMemoizer memoizer; - CountingFunc counter; - auto func = counter.func(); + for (int i = 0; i < num_threads; ++i) { + threads.emplace_back( + [&memoized_result, expected_result, &expensive_lambda]() { + const int& actual_result = memoized_result.memoize(expensive_lambda); - const auto hardware_concurrency = std::thread::hardware_concurrency(); - const int num_threads = hardware_concurrency != 0 ? hardware_concurrency : 4; - std::vector threads; - CountDownLatch latch(num_threads); - std::atomic value_has_been_memoized{false}; - for (auto i = num_threads; i > 0; i--) { - threads.emplace_back([&, i] { - latch.arrive_and_wait(); - for (int j = 0; j < 100; j++) { - if (value_has_been_memoized.load(std::memory_order_acquire)) { - const auto invocation_count_before = counter.invocation_count(); - memoizer.value(func); - SCOPED_TRACE("thread i=" + std::to_string(i) + - " j=" + std::to_string(j)); - EXPECT_EQ(counter.invocation_count(), invocation_count_before); - } else { - memoizer.value(func); - value_has_been_memoized.store(true, std::memory_order_release); - } - } - }); + // Verify that all threads get the same memoized result. + EXPECT_EQ(actual_result, expected_result); + }); } for (auto& thread : threads) { @@ -115,302 +57,6 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { } } -TEST(ThreadSafeMemoizerTest, - CopyConstructor_NoMemoizedValue_OriginalMemoizesFirst) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy_dest(memoizer); - - EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); - - EXPECT_GT(memoizer_counter.invocation_count(), 0); - EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, - CopyConstructor_NoMemoizedValue_CopyMemoizesFirst) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy_dest(memoizer); - - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); - EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - - EXPECT_GT(memoizer_counter.invocation_count(), 0); - EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, CopyConstructor_MemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - memoizer.value(memoizer_counter.func()); - ThreadSafeMemoizer memoizer_copy_dest(memoizer); - - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); - - EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, MoveConstructor_NoMemoizedValue) { - CountingFunc memoizer_move_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_move_dest(std::move(memoizer)); - - EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "bbb"); - - EXPECT_GT(memoizer_move_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, MoveConstructor_MemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - memoizer.value(memoizer_counter.func()); - ThreadSafeMemoizer memoizer_move_dest(std::move(memoizer)); - - EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "aaa"); - - EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, - CopyAssignment_NoMemoizedValueToNoMemoizedValue_OriginalMemoizesFirst) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy_dest; - - memoizer_copy_dest = memoizer; - - EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); -} - -TEST(ThreadSafeMemoizerTest, - CopyAssignment_NoMemoizedValueToNoMemoizedValue_CopyMemoizesFirst) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy_dest; - - memoizer_copy_dest = memoizer; - - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); - EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); -} - -TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToNoMemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - memoizer.value(memoizer_counter.func()); - const auto expected_memoizer_counter_invocation_count = - memoizer_counter.invocation_count(); - ThreadSafeMemoizer memoizer_copy_dest; - - memoizer_copy_dest = memoizer; - - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); - EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_EQ(memoizer_counter.invocation_count(), - expected_memoizer_counter_invocation_count); - EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, CopyAssignment_NoMemoizedValueToMemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter1("bbb1"), - memoizer_copy_dest_counter2("bbb2"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_copy_dest; - memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); - - memoizer_copy_dest = memoizer; - - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), - "bbb2"); - EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); -} - -TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToMemoizedValue) { - CountingFunc memoizer_counter1("aaa1"), memoizer_counter2("aaa2"), - memoizer_copy_dest_counter1("bbb1"), memoizer_copy_dest_counter2("bbb2"); - ThreadSafeMemoizer memoizer; - memoizer.value(memoizer_counter1.func()); - const auto expected_memoizer_counter1_invocation_count = - memoizer_counter1.invocation_count(); - ThreadSafeMemoizer memoizer_copy_dest; - memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); - const auto expected_memoizer_copy_dest_counter1_invocation_count = - memoizer_copy_dest_counter1.invocation_count(); - - memoizer_copy_dest = memoizer; - - EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), - "aaa1"); - EXPECT_EQ(memoizer.value(memoizer_counter2.func()), "aaa1"); - EXPECT_EQ(memoizer_counter1.invocation_count(), - expected_memoizer_counter1_invocation_count); - EXPECT_EQ(memoizer_copy_dest_counter1.invocation_count(), - expected_memoizer_copy_dest_counter1_invocation_count); -} - -TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToNoMemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter("bbb"); - ThreadSafeMemoizer memoizer; - memoizer.value(memoizer_counter.func()); - ThreadSafeMemoizer memoizer_move_dest; - - memoizer_move_dest = std::move(memoizer); - - EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "aaa"); - EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTest, MoveAssignment_NoMemoizedValueToMemoizedValue) { - CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter1("bbb1"), - memoizer_move_dest_counter2("bbb2"); - ThreadSafeMemoizer memoizer; - ThreadSafeMemoizer memoizer_move_dest; - memoizer_move_dest.value(memoizer_move_dest_counter1.func()); - - memoizer_move_dest = std::move(memoizer); - - EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), - "bbb2"); -} - -TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToMemoizedValue) { - CountingFunc memoizer_counter1("aaa1"), memoizer_counter2("aaa2"), - memoizer_move_dest_counter1("bbb1"), memoizer_move_dest_counter2("bbb2"); - ThreadSafeMemoizer memoizer; - memoizer.value(memoizer_counter1.func()); - const auto expected_memoizer_counter1_invocation_count = - memoizer_counter1.invocation_count(); - ThreadSafeMemoizer memoizer_move_dest; - memoizer_move_dest.value(memoizer_move_dest_counter1.func()); - const auto expected_memoizer_move_dest_counter1_invocation_count = - memoizer_move_dest_counter1.invocation_count(); - - memoizer_move_dest = std::move(memoizer); - - EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), - "aaa1"); - EXPECT_EQ(memoizer_counter1.invocation_count(), - expected_memoizer_counter1_invocation_count); - EXPECT_EQ(memoizer_move_dest_counter1.invocation_count(), - expected_memoizer_move_dest_counter1_invocation_count); -} - -TEST(ThreadSafeMemoizerTest, - CopyConstructor_CopySourceKeepsMemoizedValueAlive) { - CountingFunc memoizer_counter; - std::atomic destroyed{false}; - auto memoizer = std::make_unique>(); - memoizer->value([&] { return std::make_shared(destroyed); }); - - auto memoizer_copy_dest = - std::make_unique>(*memoizer); - - ASSERT_FALSE(destroyed.load()); - memoizer_copy_dest.reset(); - ASSERT_FALSE(destroyed.load()); - memoizer.reset(); - ASSERT_TRUE(destroyed.load()); -} - -TEST(ThreadSafeMemoizerTest, CopyAssignment_CopySourceKeepsMemoizedValueAlive) { - CountingFunc memoizer_counter; - std::atomic destroyed{false}; - auto memoizer = std::make_unique>(); - memoizer->value([&] { return std::make_shared(destroyed); }); - auto memoizer_copy_dest = - std::make_unique>(); - - *memoizer_copy_dest = *memoizer; - - ASSERT_FALSE(destroyed.load()); - memoizer_copy_dest.reset(); - ASSERT_FALSE(destroyed.load()); - memoizer.reset(); - ASSERT_TRUE(destroyed.load()); -} - -TEST(ThreadSafeMemoizerTest, - MoveConstructor_MoveSourceDoesNotKeepMemoizedValueAlive) { - CountingFunc memoizer_counter; - std::atomic destroyed{false}; - ThreadSafeMemoizer memoizer; - memoizer.value([&] { return std::make_shared(destroyed); }); - - auto memoizer_move_dest = - std::make_unique>( - std::move(memoizer)); - - ASSERT_FALSE(destroyed.load()); - memoizer_move_dest.reset(); - ASSERT_TRUE(destroyed.load()); -} - -TEST(ThreadSafeMemoizerTest, - MoveAssignment_MoveSourceDoesNotKeepMemoizedValueAlive) { - CountingFunc memoizer_counter; - std::atomic destroyed{false}; - ThreadSafeMemoizer memoizer; - memoizer.value([&] { return std::make_shared(destroyed); }); - auto memoizer_move_dest = - std::make_unique>(); - - *memoizer_move_dest = std::move(memoizer); - - ASSERT_FALSE(destroyed.load()); - memoizer_move_dest.reset(); - ASSERT_TRUE(destroyed.load()); -} - -TEST(ThreadSafeMemoizerTest, TSAN_ConcurrentCallsToValueShouldNotDataRace) { - ThreadSafeMemoizer memoizer; - const auto num_threads = max_practical_parallel_threads_for_testing() * 4; - CountDownLatch latch(num_threads); - std::vector threads; - for (auto i = num_threads; i > 0; --i) { - threads.emplace_back([i, &latch, &memoizer] { - latch.arrive_and_wait(); - memoizer.value([i] { return std::make_shared(i); }); - }); - } - for (auto&& thread : threads) { - thread.join(); - } -} - -TEST(ThreadSafeMemoizerTest, TSAN_ValueInACopyShouldNotDataRace) { - ThreadSafeMemoizer memoizer; - memoizer.value([&] { return std::make_shared(1111); }); - std::unique_ptr> memoizer_copy; - // NOTE: Always use std::memory_order_relaxed when loading from and storing - // into this variable to avoid creating a happens-before releationship, which - // would defeat the purpose of this test. - std::atomic*> memoizer_copy_atomic(nullptr); - - std::thread thread1([&] { - memoizer_copy = std::make_unique>(memoizer); - memoizer_copy_atomic.store(memoizer_copy.get(), std::memory_order_relaxed); - }); - std::thread thread2([&] { - ThreadSafeMemoizer* memoizer_ptr = nullptr; - while (true) { - memoizer_ptr = memoizer_copy_atomic.load(std::memory_order_relaxed); - if (memoizer_ptr) { - break; - } - std::this_thread::yield(); - } - memoizer_ptr->value([&] { return std::make_shared(2222); }); - }); - - thread1.join(); - thread2.join(); - - const auto memoizer_copy_value = - memoizer_copy->value([&] { return std::make_shared(3333); }); - EXPECT_EQ(memoizer_copy_value, 1111); -} - -} // namespace +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc deleted file mode 100644 index 5557c6647c6..00000000000 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace firebase { -namespace firestore { -namespace testing { -namespace { - -std::vector Split(const std::string& s, const std::string& sep) { - std::vector chunks; - auto index = s.find(sep); - decltype(index) start = 0; - while (index != std::string::npos) { - chunks.push_back(s.substr(start, index - start)); - start = index + sep.size(); - index = s.find(sep, start); - } - chunks.push_back(s.substr(start)); - return chunks; -} - -} // namespace - -CountingFunc::CountingFunc(const std::string& format) - : CountingFunc(Split(format, "%s")) { -} - -CountingFunc::CountingFunc(std::vector chunks) - : chunks_(std::move(chunks)) { - assert(!chunks_.empty()); - // Explicitly store the initial value into count_ because initialization of - // std::atomic is _not_ atomic. - count_.store(0); -} - -std::function()> CountingFunc::func() { - return [&] { return std::make_shared(Next()); }; -} - -int CountingFunc::invocation_count() const { - return count_.load(); -} - -std::string CountingFunc::Next() { - const int id = count_.fetch_add(1, std::memory_order_acq_rel); - std::ostringstream ss; - int index = 0; - for (const std::string& chunk : chunks_) { - if (index > 0) { - ss << id; - } - index++; - ss << chunk; - } - return ss.str(); -} - -CountDownLatch::CountDownLatch(int count) { - // Explicitly store the count into the atomic because initialization is - // NOT atomic. - count_.store(count); -} - -void CountDownLatch::arrive_and_wait() { - count_.fetch_sub(1, std::memory_order_acq_rel); - while (count_.load(std::memory_order_acquire) > 0) { - std::this_thread::yield(); - } -} - -decltype(std::thread::hardware_concurrency()) -max_practical_parallel_threads_for_testing() { - const auto hardware_concurrency = std::thread::hardware_concurrency(); - return hardware_concurrency != 0 ? hardware_concurrency : 4; -} - -} // namespace testing -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h deleted file mode 100644 index 298aac532c8..00000000000 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ -#define FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ - -#include - -#include -#include -#include -#include -#include -#include - -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { -namespace testing { - -#if defined(GTEST_USES_SIMPLE_RE) || defined(GTEST_USES_RE2) -constexpr const char* FST_RE_DIGIT = "\\d"; -#elif defined(GTEST_USES_POSIX_RE) -constexpr const char* FST_RE_DIGIT = "[[:digit:]]"; -#endif - -/** - * Generates strings that incorporate a count in a thread-safe manner. - * - * The "format" string given to the constructor is literally generated, except - * that all occurrences of "%s" are replaced with the invocation count. - * - * All functions in this class may be safely called concurrently by multiple - * threads. - */ -class CountingFunc { - public: - /** - * Creates a new `CountingFunc` that generates strings that are equal to - * the base-10 string representation of the invocation count. - */ - CountingFunc() : CountingFunc("%s") { - } - - /** - * Creates a new `CountingFunc` that generates strings that match the given - * format. - * @param format the format to use when generating strings; all occurrences of - * "%s" will be replaced by the count, which starts at 0 (zero). - */ - explicit CountingFunc(const std::string& format); - - /** - * Returns a function that, when invoked, generates a string using the format - * given to the constructor. Every string returned by the function has a - * different count. - * - * Although each invocation of this function _may_ return a distinct function, - * they all use the same counter and may be safely called concurrently from - * multiple threads. - * - * The returned function is valid as long as this `CountingFunc` object is - * valid. - */ - std::function()> func(); - - /** - * Returns the total number of invocations that have occurred on functions - * returned by `func()`. A new instance of this class will return 0 (zero). - */ - int invocation_count() const; - - private: - std::atomic count_; - std::vector chunks_; - - explicit CountingFunc(std::vector chunks); - std::string Next(); -}; - -/** - * A simple implementation of std::latch in C++20. - */ -class CountDownLatch { - public: - explicit CountDownLatch(int count); - void arrive_and_wait(); - - private: - std::atomic count_; -}; - -class SetOnDestructor { - public: - explicit SetOnDestructor(std::atomic& flag) : flag_(flag) { - } - - ~SetOnDestructor() { - flag_.store(true); - } - - private: - std::atomic& flag_; -}; - -/** - * Returns the largest number of threads that can be truly executed in parallel, - * or an arbitrary value greater than one if the number of CPU cores cannot be - * determined. - */ -decltype(std::thread::hardware_concurrency()) -max_practical_parallel_threads_for_testing(); - -} // namespace testing -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc deleted file mode 100644 index 15bce14b419..00000000000 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" - -#include -#include -#include - -#include "gtest/gtest.h" - -namespace { - -using firebase::firestore::testing::CountDownLatch; -using firebase::firestore::testing::CountingFunc; -using firebase::firestore::testing::max_practical_parallel_threads_for_testing; - -TEST(ThreadSafeMemoizerTesting, DefaultConstructor) { - CountingFunc counting_func; - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - const auto i_str = std::to_string(i); - SCOPED_TRACE("iteration i=" + i_str); - EXPECT_EQ(*func(), i_str); - } -} - -TEST(ThreadSafeMemoizerTesting, - CountingFuncShouldReturnSameStringIfNoReplacements) { - CountingFunc counting_func("tdjebqrtny"); - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - SCOPED_TRACE("iteration i=" + std::to_string(i)); - EXPECT_EQ(*func(), "tdjebqrtny"); - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesReplacementAtStart) { - CountingFunc counting_func("%scmgb5bsbj2"); - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - const auto i_str = std::to_string(i); - SCOPED_TRACE("iteration i=" + i_str); - EXPECT_EQ(*func(), i_str + "cmgb5bsbj2"); - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesReplacementAtEnd) { - CountingFunc counting_func("nd3krmj2mn%s"); - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - const auto i_str = std::to_string(i); - SCOPED_TRACE("iteration i=" + i_str); - EXPECT_EQ(*func(), "nd3krmj2mn" + i_str); - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesReplacementInMiddle) { - CountingFunc counting_func("txxz4%sddrs5"); - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - const auto i_str = std::to_string(i); - SCOPED_TRACE("iteration i=" + i_str); - EXPECT_EQ(*func(), "txxz4" + i_str + "ddrs5"); - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesMultipleReplacements) { - CountingFunc counting_func("%scx%s3b%s5jazwf%s"); - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - const auto i_str = std::to_string(i); - SCOPED_TRACE("iteration i=" + i_str); - EXPECT_EQ(*func(), i_str + "cx" + i_str + "3b" + i_str + "5jazwf" + i_str); - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncFunctionsUseSameCounter) { - CountingFunc counting_func("3gswsz9hyd_%s"); - const std::vector funcs{ - counting_func.func(), counting_func.func(), counting_func.func(), - counting_func.func(), counting_func.func()}; - int next_id = 0; - for (int i = 0; i < 100; i++) { - for (decltype(funcs.size()) j = 0; j < funcs.size(); j++) { - SCOPED_TRACE("iteration i=" + std::to_string(i) + - " j=" + std::to_string(j)); - EXPECT_EQ(*funcs[j](), "3gswsz9hyd_" + std::to_string(next_id++)); - } - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncThreadSafety) { - CountingFunc counting_func("ejrxk3g6tb_%s"); - std::vector threads; - std::array, 20> strings; - CountDownLatch latch(strings.size()); - for (decltype(strings.size()) i = 0; i < strings.size(); i++) { - threads.emplace_back([&, i] { - auto func = counting_func.func(); - auto& results = strings[i]; - latch.arrive_and_wait(); - for (decltype(results.size()) j = 0; j < results.size(); j++) { - results[j] = *func(); - } - }); - } - - for (auto& thread : threads) { - thread.join(); - } - - std::vector actual_strings; - for (const auto& thread_strings : strings) { - actual_strings.insert(actual_strings.end(), thread_strings.begin(), - thread_strings.end()); - } - - std::vector expected_strings; - for (decltype(actual_strings.size()) i = 0; i < actual_strings.size(); i++) { - expected_strings.push_back("ejrxk3g6tb_" + std::to_string(i)); - } - - std::sort(actual_strings.begin(), actual_strings.end()); - std::sort(expected_strings.begin(), expected_strings.end()); - ASSERT_EQ(actual_strings, expected_strings); -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountOnNewInstance) { - CountingFunc counting_func; - EXPECT_EQ(counting_func.invocation_count(), 0); -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountIncrementsBy1) { - CountingFunc counting_func; - auto func = counting_func.func(); - for (int i = 0; i < 100; i++) { - EXPECT_EQ(counting_func.invocation_count(), i); - func(); - EXPECT_EQ(counting_func.invocation_count(), i + 1); - } -} - -TEST(ThreadSafeMemoizerTesting, - CountingFuncInvocationCountIncrementedByEachFunc) { - CountingFunc counting_func; - for (int i = 0; i < 100; i++) { - auto func = counting_func.func(); - EXPECT_EQ(counting_func.invocation_count(), i); - func(); - EXPECT_EQ(counting_func.invocation_count(), i + 1); - } -} - -TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountThreadSafe) { - CountingFunc counting_func; - const int num_threads = max_practical_parallel_threads_for_testing(); - std::vector threads; - CountDownLatch latch(num_threads); - for (auto i = num_threads; i > 0; i--) { - threads.emplace_back([&, i] { - auto func = counting_func.func(); - latch.arrive_and_wait(); - auto last_count = counting_func.invocation_count(); - for (int j = 0; j < 100; j++) { - SCOPED_TRACE("Thread i=" + std::to_string(i) + - " j=" + std::to_string(j)); - func(); - auto new_count = counting_func.invocation_count(); - EXPECT_GT(new_count, last_count); - last_count = new_count; - } - }); - } - - for (auto& thread : threads) { - thread.join(); - } - - EXPECT_EQ(counting_func.invocation_count(), num_threads * 100); -} - -} // namespace diff --git a/cmake/compiler_setup.cmake b/cmake/compiler_setup.cmake index 3c74d4698ac..f214c55ca66 100644 --- a/cmake/compiler_setup.cmake +++ b/cmake/compiler_setup.cmake @@ -45,20 +45,10 @@ if(CXX_CLANG OR CXX_GNU) -Wuninitialized -fno-common - # Don't fail the build solely on account of using deprecated things. - -Wno-error=deprecated-declarations - # Delete unused things -Wunused-function -Wunused-value -Wunused-variable ) - # Disable "redundant-move" warning since it's not really a problem, and is even a valid coding - # style to over-use std::move() in case the type is ever changed to become non-trivially moveable. - CHECK_CXX_COMPILER_FLAG("-Wno-redundant-move" FIREBASE_CXX_COMPILER_FLAG_NO_REDUNDANT_MOVE_SUPPORTED) - if(FIREBASE_CXX_COMPILER_FLAG_NO_REDUNDANT_MOVE_SUPPORTED) - list(APPEND common_flags -Wno-redundant-move) - endif() - set( cxx_flags -Wreorder -Werror=reorder @@ -88,6 +78,14 @@ if(CXX_CLANG OR CXX_GNU) list(APPEND common_flags -fdiagnostics-color) endif() endif() + + # Disable treating "redundant-move" as an error since it's not really a problem, + # and is even a valid coding style to over-use std::move() in case the type is + # ever changed to become non-trivially moveable. + CHECK_CXX_COMPILER_FLAG("-Wno-error=redundant-move" FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) + if(FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) + list(APPEND common_flags -Wno-error=redundant-move) + endif() endif() if(APPLE) diff --git a/scripts/check.sh b/scripts/check.sh index 76184979d98..59cad32aa93 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -293,21 +293,8 @@ python --version "${top_dir}/scripts/check_imports.swift" # Google C++ style - -# If cpplint.py itself changed, then run it on the entire repo. -if [[ "$GITHUB_EVENT_NAME" != pull_request ]] ; then - CHECK_LINT_CHANGED=0 -elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/check_lint.py') ]]; then - CHECK_LINT_CHANGED=1 -elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/cpplint.py') ]]; then - CHECK_LINT_CHANGED=1 -else - CHECK_LINT_CHANGED=0 -fi - lint_cmd=("${top_dir}/scripts/check_lint.py") -if [[ "$CHECK_DIFF" == true ]] && [[ "$CHECK_LINT_CHANGED" == 0 ]] ; then +if [[ "$CHECK_DIFF" == true ]]; then lint_cmd+=("${START_SHA}") fi - "${lint_cmd[@]}" diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 52010c3883c..07e397599e2 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -35,6 +35,7 @@ import math # for log import os import re +import sre_compile import string import sys import sysconfig @@ -283,6 +284,7 @@ 'build/include_order', 'build/include_what_you_use', 'build/namespaces_headers', + 'build/namespaces_literals', 'build/namespaces', 'build/printf_format', 'build/storage_class', @@ -1026,7 +1028,7 @@ def Match(pattern, s): # performance reasons; factoring it out into a separate function turns out # to be noticeably expensive. if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = re.compile(pattern) + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) return _regexp_compile_cache[pattern].match(s) @@ -1044,14 +1046,14 @@ def ReplaceAll(pattern, rep, s): string with replacements made (or original string if no replacements) """ if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = re.compile(pattern) + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) return _regexp_compile_cache[pattern].sub(rep, s) def Search(pattern, s): """Searches the string for the pattern, caching the compiled regexp.""" if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = re.compile(pattern) + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) return _regexp_compile_cache[pattern].search(s) @@ -5354,10 +5356,15 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, 'Did you mean "memset(%s, 0, %s)"?' % (match.group(1), match.group(2))) - if Search(r'\busing namespace\b', line) and not Search(r'\b::\w+_literals\b', line): - error(filename, linenum, 'build/namespaces', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') + if Search(r'\busing namespace\b', line): + if Search(r'\bliterals\b', line): + error(filename, linenum, 'build/namespaces_literals', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + else: + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') # Detect variable-length arrays. match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) @@ -6313,6 +6320,87 @@ def ProcessLine(filename, file_extension, clean_lines, line, for check_fn in extra_check_functions: check_fn(filename, clean_lines, line, error) +def FlagCxx11Features(filename, clean_lines, linenum, error): + """Flag those c++11 features that we only allow in certain places. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++ TR1 headers. + if include and include.group(1).startswith('tr1/'): + error(filename, linenum, 'build/c++tr1', 5, + ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) + + # Flag unapproved C++11 headers. + if include and include.group(1) in ('cfenv', + 'condition_variable', + 'fenv.h', + 'future', + 'mutex', + 'thread', + 'chrono', + 'ratio', + 'regex', + 'system_error', + ): + error(filename, linenum, 'build/c++11', 5, + ('<%s> is an unapproved C++11 header.') % include.group(1)) + + # The only place where we need to worry about C++11 keywords and library + # features in preprocessor directives is in macro definitions. + if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return + + # These are classes and free functions. The classes are always + # mentioned as std::*, but we only catch the free functions if + # they're not found by ADL. They're alphabetical by header. + for top_name in ( + # type_traits + 'alignment_of', + 'aligned_union', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++11', 5, + ('std::%s is an unapproved C++11 class or function. Send c-style ' + 'an example of where it would make your code more readable, and ' + 'they may let you use it.') % top_name) + + +def FlagCxx14Features(filename, clean_lines, linenum, error): + """Flag those C++14 features that we restrict. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++14 headers. + if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): + error(filename, linenum, 'build/c++14', 5, + ('<%s> is an unapproved C++14 header.') % include.group(1)) + + # These are classes and free functions with abseil equivalents. + for top_name in ( + # memory + 'make_unique', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++14', 5, + 'std::%s does not exist in C++11. Use absl::%s instead.' % + (top_name, top_name)) + + def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=None): """Performs lint checks and reports any errors to the given error function. @@ -6349,6 +6437,8 @@ def ProcessFileData(filename, file_extension, lines, error, ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, extra_check_functions) + FlagCxx11Features(filename, clean_lines, line, error) + FlagCxx14Features(filename, clean_lines, line, error) nesting_state.CheckCompletedBlocks(filename, error) CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) From 7f05f8ddbb1bd5f9315a5364792a1bd77da467d2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 11:05:35 -0500 Subject: [PATCH 75/84] thread_safe_memoizer.h: re-write using atomics to fix apparent memory leak detected by the xcode "leaks" tool --- Firestore/CHANGELOG.md | 1 + Firestore/core/src/core/composite_filter.cc | 19 +- Firestore/core/src/core/composite_filter.h | 3 +- Firestore/core/src/core/field_filter.cc | 11 +- Firestore/core/src/core/field_filter.h | 5 +- Firestore/core/src/core/filter.cc | 6 - Firestore/core/src/core/filter.h | 20 +- Firestore/core/src/core/query.cc | 77 ++- Firestore/core/src/core/query.h | 31 +- .../core/src/util/thread_safe_memoizer.h | 139 +++-- .../unit/util/thread_safe_memoizer_test.cc | 536 +++++++++++++++++- .../unit/util/thread_safe_memoizer_testing.cc | 125 ++++ .../unit/util/thread_safe_memoizer_testing.h | 145 +++++ .../util/thread_safe_memoizer_testing_test.cc | 277 +++++++++ 14 files changed, 1248 insertions(+), 147 deletions(-) create mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc create mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing.h create mode 100644 Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index ad57793f111..2991355ae64 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- [fixed] Fixed memory leak in `Query.whereField()`. (#13978) - [fixed] Fixed use-after-free bug when internally formatting strings. (#14306) # 11.6.0 diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 7bbb81abc57..02186989330 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -17,6 +17,7 @@ #include "Firestore/core/src/core/composite_filter.h" #include +#include #include #include "Firestore/core/src/core/field_filter.h" @@ -141,16 +142,14 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter( return nullptr; } -const std::vector& CompositeFilter::Rep::GetFlattenedFilters() - const { - return memoized_flattened_filters_->memoize([&]() { - std::vector flattened_filters; - for (const auto& filter : filters()) - std::copy(filter.GetFlattenedFilters().begin(), - filter.GetFlattenedFilters().end(), - std::back_inserter(flattened_filters)); - return flattened_filters; - }); +std::shared_ptr> +CompositeFilter::Rep::CalculateFlattenedFilters() const { + auto flattened_filters = std::make_shared>(); + for (const auto& filter : filters()) + std::copy(filter.GetFlattenedFilters().begin(), + filter.GetFlattenedFilters().end(), + std::back_inserter(*flattened_filters)); + return flattened_filters; } } // namespace core diff --git a/Firestore/core/src/core/composite_filter.h b/Firestore/core/src/core/composite_filter.h index 24671d3e44e..2858271a358 100644 --- a/Firestore/core/src/core/composite_filter.h +++ b/Firestore/core/src/core/composite_filter.h @@ -138,7 +138,8 @@ class CompositeFilter : public Filter { return filters_.empty(); } - const std::vector& GetFlattenedFilters() const override; + std::shared_ptr> CalculateFlattenedFilters() + const override; std::vector GetFilters() const override { return filters(); diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index b68956c8a52..2867e3ba8ba 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -16,6 +16,7 @@ #include "Firestore/core/src/core/field_filter.h" +#include #include #include "Firestore/core/src/core/array_contains_any_filter.h" @@ -122,12 +123,12 @@ FieldFilter::FieldFilter(std::shared_ptr rep) : Filter(std::move(rep)) { } -const std::vector& FieldFilter::Rep::GetFlattenedFilters() const { +std::shared_ptr> +FieldFilter::Rep::CalculateFlattenedFilters() const { // This is already a field filter, so we return a vector of size one. - return memoized_flattened_filters_->memoize([&]() { - return std::vector{ - FieldFilter(std::make_shared(*this))}; - }); + auto filters = std::make_shared>(); + filters->push_back(FieldFilter(std::make_shared(*this))); + return filters; } std::vector FieldFilter::Rep::GetFilters() const { diff --git a/Firestore/core/src/core/field_filter.h b/Firestore/core/src/core/field_filter.h index 48219f222f2..2f03254e1a1 100644 --- a/Firestore/core/src/core/field_filter.h +++ b/Firestore/core/src/core/field_filter.h @@ -117,8 +117,6 @@ class FieldFilter : public Filter { return false; } - const std::vector& GetFlattenedFilters() const override; - std::vector GetFilters() const override; protected: @@ -140,6 +138,9 @@ class FieldFilter : public Filter { bool MatchesComparison(util::ComparisonResult comparison) const; + std::shared_ptr> CalculateFlattenedFilters() + const override; + private: friend class FieldFilter; diff --git a/Firestore/core/src/core/filter.cc b/Firestore/core/src/core/filter.cc index a77ccc55e34..853c15b31ba 100644 --- a/Firestore/core/src/core/filter.cc +++ b/Firestore/core/src/core/filter.cc @@ -35,12 +35,6 @@ std::ostream& operator<<(std::ostream& os, const Filter& filter) { return os << filter.ToString(); } -Filter::Rep::Rep() - : memoized_flattened_filters_( - std::make_shared< - util::ThreadSafeMemoizer>>()) { -} - } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index ec20120daf6..bab79599cc6 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_CORE_FILTER_H_ #define FIRESTORE_CORE_SRC_CORE_FILTER_H_ +#include #include #include #include @@ -114,7 +115,7 @@ class Filter { protected: class Rep { public: - Rep(); + Rep() = default; virtual ~Rep() = default; @@ -147,20 +148,23 @@ class Filter { virtual bool IsEmpty() const = 0; - virtual const std::vector& GetFlattenedFilters() const = 0; + virtual const std::vector& GetFlattenedFilters() const { + const auto func = std::bind(&Rep::CalculateFlattenedFilters, this); + return memoized_flattened_filters_.value(func); + } virtual std::vector GetFilters() const = 0; + protected: + virtual std::shared_ptr> + CalculateFlattenedFilters() const = 0; + + private: /** * Memoized list of all field filters that can be found by * traversing the tree of filters contained in this composite filter. - * - * Use a `std::shared_ptr` rather than using - * `ThreadSafeMemoizer` directly so that this class is copyable - * (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag` - * member variable, which is not copyable). */ - mutable std::shared_ptr>> + mutable util::ThreadSafeMemoizer> memoized_flattened_filters_; }; diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 5b70ebc322f..1c8394748d9 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -17,6 +17,7 @@ #include "Firestore/core/src/core/query.h" #include +#include #include #include "Firestore/core/src/core/bound.h" @@ -91,44 +92,42 @@ absl::optional Query::FindOpInsideFilters( return absl::nullopt; } -const std::vector& Query::normalized_order_bys() const { - return memoized_normalized_order_bys_->memoize([&]() { - // Any explicit order by fields should be added as is. - std::vector result = explicit_order_bys_; - std::set fieldsNormalized; - for (const OrderBy& order_by : explicit_order_bys_) { - fieldsNormalized.insert(order_by.field()); - } +std::shared_ptr> Query::CalculateNormalizedOrderBys() + const { + // Any explicit order by fields should be added as is. + auto result = std::make_shared>(explicit_order_bys_); + std::set fieldsNormalized; + for (const OrderBy& order_by : explicit_order_bys_) { + fieldsNormalized.insert(order_by.field()); + } - // The order of the implicit ordering always matches the last explicit order - // by. - Direction last_direction = explicit_order_bys_.empty() - ? Direction::Ascending - : explicit_order_bys_.back().direction(); - - // Any inequality fields not explicitly ordered should be implicitly ordered - // in a lexicographical order. When there are multiple inequality filters on - // the same field, the field should be added only once. Note: - // `std::set` sorts the key field before other fields. - // However, we want the key field to be sorted last. - const std::set inequality_fields = - InequalityFilterFields(); - - for (const model::FieldPath& field : inequality_fields) { - if (fieldsNormalized.find(field) == fieldsNormalized.end() && - !field.IsKeyFieldPath()) { - result.push_back(OrderBy(field, last_direction)); - } + // The order of the implicit ordering always matches the last explicit order + // by. + Direction last_direction = explicit_order_bys_.empty() + ? Direction::Ascending + : explicit_order_bys_.back().direction(); + + // Any inequality fields not explicitly ordered should be implicitly ordered + // in a lexicographical order. When there are multiple inequality filters on + // the same field, the field should be added only once. Note: + // `std::set` sorts the key field before other fields. + // However, we want the key field to be sorted last. + const std::set inequality_fields = InequalityFilterFields(); + + for (const model::FieldPath& field : inequality_fields) { + if (fieldsNormalized.find(field) == fieldsNormalized.end() && + !field.IsKeyFieldPath()) { + result->push_back(OrderBy(field, last_direction)); } + } - // Add the document key field to the last if it is not explicitly ordered. - if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == - fieldsNormalized.end()) { - result.push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); - } + // Add the document key field to the last if it is not explicitly ordered. + if (fieldsNormalized.find(FieldPath::KeyFieldPath()) == + fieldsNormalized.end()) { + result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction)); + } - return result; - }); + return result; } LimitType Query::limit_type() const { @@ -296,14 +295,12 @@ std::string Query::ToString() const { return absl::StrCat("Query(canonical_id=", CanonicalId(), ")"); } -const Target& Query::ToTarget() const& { - return memoized_target_->memoize( - [&]() { return ToTarget(normalized_order_bys()); }); +std::shared_ptr Query::CalculateTarget() const { + return std::make_shared(ToTarget(normalized_order_bys())); } -const Target& Query::ToAggregateTarget() const& { - return memoized_aggregate_target_->memoize( - [&]() { return ToTarget(explicit_order_bys_); }); +std::shared_ptr Query::CalculateAggregateTarget() const { + return std::make_shared(ToTarget(explicit_order_bys_)); } Target Query::ToTarget(const std::vector& order_bys) const { diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index 23351a4de56..d2b7b3247ff 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_CORE_QUERY_H_ #define FIRESTORE_CORE_SRC_CORE_QUERY_H_ +#include #include #include #include @@ -148,7 +149,10 @@ class Query { * This might include additional sort orders added implicitly to match the * backend behavior. */ - const std::vector& normalized_order_bys() const; + const std::vector& normalized_order_bys() const { + const auto func = std::bind(&Query::CalculateNormalizedOrderBys, this); + return memoized_normalized_order_bys_.value(func); + } bool has_limit() const { return limit_ != Target::kNoLimit; @@ -246,7 +250,10 @@ class Query { * Returns a `Target` instance this query will be mapped to in backend * and local store. */ - const Target& ToTarget() const&; + const Target& ToTarget() const& { + const auto func = std::bind(&Query::CalculateTarget, this); + return memoized_target_.value(func); + } /** * Returns a `Target` instance this query will be mapped to in backend @@ -254,7 +261,10 @@ class Query { * for non-aggregate queries, aggregate query targets do not contain * normalized order-bys, they only contain explicit order-bys. */ - const Target& ToAggregateTarget() const&; + const Target& ToAggregateTarget() const& { + const auto func = std::bind(&Query::CalculateAggregateTarget, this); + return memoized_aggregate_target_.value(func); + } friend std::ostream& operator<<(std::ostream& os, const Query& query); @@ -295,20 +305,19 @@ class Query { // member variable, which is not copyable). // The memoized list of sort orders. - mutable std::shared_ptr>> - memoized_normalized_order_bys_{ - std::make_shared>>()}; + std::shared_ptr> CalculateNormalizedOrderBys() const; + mutable util::ThreadSafeMemoizer> + memoized_normalized_order_bys_; // The corresponding Target of this Query instance. - mutable std::shared_ptr> memoized_target_{ - std::make_shared>()}; + std::shared_ptr CalculateTarget() const; + mutable util::ThreadSafeMemoizer memoized_target_; // The corresponding aggregate Target of this Query instance. Unlike targets // for non-aggregate queries, aggregate query targets do not contain // normalized order-bys, they only contain explicit order-bys. - mutable std::shared_ptr> - memoized_aggregate_target_{ - std::make_shared>()}; + std::shared_ptr CalculateAggregateTarget() const; + mutable util::ThreadSafeMemoizer memoized_aggregate_target_; }; bool operator==(const Query& lhs, const Query& rhs); diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index 5bc6fc1c529..f99497f8472 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ #define FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_ #include -#include // NOLINT(build/c++11) -#include +#include +#include namespace firebase { namespace firestore { @@ -28,51 +28,120 @@ namespace util { /** * Stores a memoized value in a manner that is safe to be shared between * multiple threads. - * - * TODO(b/299933587) Make `ThreadSafeMemoizer` copyable and moveable. */ template class ThreadSafeMemoizer { public: - ThreadSafeMemoizer() = default; - - ~ThreadSafeMemoizer() { - // Call `std::call_once` in order to synchronize with the "active" - // invocation of `memoize()`. Without this synchronization, there is a data - // race between this destructor, which "reads" `memoized_value_` to destroy - // it, and the write to `memoized_value_` done by the "active" invocation of - // `memoize()`. - std::call_once(once_, [&]() {}); + /** + * Creates a new ThreadSafeMemoizer with no memoized value. + */ + ThreadSafeMemoizer() { + std::atomic_store(&memoized_, std::shared_ptr()); + } + + /** + * Copy constructor: creates a new ThreadSafeMemoizer object with the same + * memoized value as the ThreadSafeMemoizer object referred to by the given + * reference. + * + * The runtime performance of this function is O(1). + */ + ThreadSafeMemoizer(const ThreadSafeMemoizer& other) { + operator=(other); + } + + /** + * Copy assignment operator: replaces this object's memoized value with the + * memoized value of the ThreadSafeMemoizer object referred to by the given + * reference. + * + * The runtime performance of this function is O(1). + */ + ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer& other) { + if (&other == this) { + return *this; + } + + std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); + return *this; + } + + /** + * Move constructor: creates a new ThreadSafeMemoizer object with the same + * memoized value as the ThreadSafeMemoizer object referred to by the given + * reference, also clearing its memoized value. + * + * The runtime performance of this function is O(1). + */ + ThreadSafeMemoizer(ThreadSafeMemoizer&& other) noexcept { + operator=(std::move(other)); } - // This class cannot be copied or moved, because it has `std::once_flag` - // member. - ThreadSafeMemoizer(const ThreadSafeMemoizer&) = delete; - ThreadSafeMemoizer(ThreadSafeMemoizer&&) = delete; - ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer&) = delete; - ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&&) = delete; + /** + * Move assignment operator: replaces this object's memoized value with the + * memoized value of the ThreadSafeMemoizer object referred to by the given + * reference, also clearing its memoized value. + * + * The runtime performance of this function is O(1). + */ + ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&& other) noexcept { + std::atomic_store(&memoized_, std::atomic_load(&other.memoized_)); + std::atomic_store(&other.memoized_, std::shared_ptr()); + return *this; + } /** - * Memoize a value. + * Return the memoized value, calculating it with the given function if + * needed. + * + * If this object _does_ have a memoized value then this function simply + * returns a reference to it and does _not_ call the given function. * - * The std::function object specified by the first invocation of this - * function (the "active" invocation) will be invoked synchronously. - * None of the std::function objects specified by the subsequent - * invocations of this function (the "passive" invocations) will be - * invoked. All invocations, both "active" and "passive", will return a - * reference to the std::vector created by copying the return value from - * the std::function specified by the "active" invocation. It is, - * therefore, the "active" invocation's job to return the std::vector - * to memoize. + * On the other hand, if this object does _not_ have a memoized value then + * the given function is called to calculate the value to memoize. The value + * returned by the function is stored internally as the "memoized value" and + * then returned. + * + * The given function *must* be idempotent because it _may_ be called more + * than once due to the semantics of "weak" compare-and-exchange. No reference + * to the given function is retained by this object. The given function will + * be called synchronously by this function, if it is called at all. + * + * This function is thread-safe and may be called concurrently by multiple + * threads. + * + * The returned reference should only be considered "valid" as long as this + * ThreadSafeMemoizer instance is alive. */ - const T& memoize(std::function func) { - std::call_once(once_, [&]() { memoized_value_ = func(); }); - return memoized_value_; + const T& value(const std::function()>& func) { + std::shared_ptr old_memoized = std::atomic_load(&memoized_); + + while (true) { + if (old_memoized) { + return *old_memoized; + } + + std::shared_ptr new_memoized = func(); + + if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, + new_memoized)) { + return *new_memoized; + } + } } private: - std::once_flag once_; - T memoized_value_; + // NOTE: Always use the std::atomic_XXX() functions to access the memoized_ + // std::shared_ptr to ensure thread safety. + // See https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic. + + // TODO(c++20): Use std::atomic> instead of a bare + // std::shared_ptr and the std::atomic_XXX() functions. The + // std::atomic_XXX() free functions are deprecated in C++20, and are also + // more error-prone than their std::atomic> member + // function counterparts. + + std::shared_ptr memoized_; }; } // namespace util diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 767fa7cb318..2c5d57c1116 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,137 @@ #include "Firestore/core/src/util/thread_safe_memoizer.h" -#include // NOLINT(build/c++11) +#include +#include +#include +#include + +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" -namespace firebase { -namespace firestore { -namespace util { +namespace { + +using namespace std::literals::string_literals; +using firebase::firestore::testing::CountDownLatch; +using firebase::firestore::testing::CountingFunc; +using firebase::firestore::testing::FST_RE_DIGIT; +using firebase::firestore::testing::GenerateRandomBool; +using firebase::firestore::testing::max_practical_parallel_threads_for_testing; +using firebase::firestore::testing::SetOnDestructor; +using firebase::firestore::util::ThreadSafeMemoizer; +using testing::MatchesRegex; +using testing::StartsWith; + +/** + * Performs a copy or move assignment (chosen randomly) on the given memoizer + * and then ensure that it behaves as expected. This is useful for testing the + * "move" logic because a move-from object, according to the C++ standard, is in + * a "valid, but unspecified" state and the only operations it is guaranteed to + * support are assignment and destruction. + */ +void VerifyWorksAfterBeingAssigned(ThreadSafeMemoizer& memoizer); + +TEST(ThreadSafeMemoizerTest, DefaultConstructor) { + ThreadSafeMemoizer memoizer; + auto func = [] { return std::make_shared(42); }; + EXPECT_EQ(memoizer.value(func), 42); +} -TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { - std::atomic global_int{77}; +TEST(ThreadSafeMemoizerTest, Value_ShouldReturnComputedValueOnFirstInvocation) { + ThreadSafeMemoizer memoizer; + CountingFunc counter("rztsygzy5z"); + EXPECT_EQ(memoizer.value(counter.func()), "rztsygzy5z"); +} - auto expensive_lambda = [&]() { - // Simulate an expensive operation - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // If the lambda gets executed multiple times, threads will see incremented - // `global_int`. - global_int++; - return global_int.load(); - }; +TEST(ThreadSafeMemoizerTest, + Value_ShouldReturnMemoizedValueOnSubsequentInvocations) { + ThreadSafeMemoizer memoizer; + CountingFunc counter("tfj6v4kdxn_%s"); + auto func = counter.func(); - const int num_threads = 5; - const int expected_result = 78; + const auto expected = memoizer.value(func); + // Do not hardcode "tfj6v4kdxn_0" as the expected value because + // ThreadSafeMemoizer.value() documents that it _may_ call the given function + // multiple times. + ASSERT_THAT(memoizer.value(func), + MatchesRegex("tfj6v4kdxn_"s + FST_RE_DIGIT + "+")); - // Create a thread safe memoizer and multiple threads. - util::ThreadSafeMemoizer memoized_result; + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + ASSERT_EQ(memoizer.value(func), expected); + } +} + +TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { + ThreadSafeMemoizer memoizer; + CountingFunc counter; + auto func = counter.func(); + memoizer.value(func); + // Do not hardcode 1 as the expected invocation count because + // ThreadSafeMemoizer.value() documents that it _may_ call the given function + // multiple times. + const auto expected_invocation_count = counter.invocation_count(); + for (int i = 0; i < 100; i++) { + memoizer.value(func); + } + EXPECT_EQ(counter.invocation_count(), expected_invocation_count); +} + +TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { + ThreadSafeMemoizer memoizer; + CountingFunc counter("jhvyg8aym4_invocation=%s_thread=%c"); + + const int num_threads = max_practical_parallel_threads_for_testing(); std::vector threads; + CountDownLatch latch(num_threads); + std::atomic has_memoized_value{false}; + + for (auto i = num_threads; i > 0; i--) { + threads.emplace_back([&, i] { + // Create a std::function that increments a local count when invoked. + const std::string thread_id = std::to_string(i); + int my_func_invocation_count = 0; + auto func = [&, wrapped_func = counter.func(thread_id)] { + my_func_invocation_count++; + return wrapped_func(); + }; - for (int i = 0; i < num_threads; ++i) { - threads.emplace_back( - [&memoized_result, expected_result, &expensive_lambda]() { - const int& actual_result = memoized_result.memoize(expensive_lambda); + // Wait for all the other threads to get here before proceeding, to + // maximize concurrent access to the ThreadSafeMemoizer object. + latch.arrive_and_wait(); - // Verify that all threads get the same memoized result. - EXPECT_EQ(actual_result, expected_result); - }); + // Make an initial invocation of memoizer.value(). If some other thread + // is known to have already set the memoized value then ensure that our + // local function is _not_ invoked; otherwise, announce to the other + // threads that there is _now_ a memoized value. + const int expected_func_invocation_count = [&] { + const bool had_memoized_value = + has_memoized_value.load(std::memory_order_acquire); + auto memoized_value = memoizer.value(func); + + SCOPED_TRACE("thread i=" + thread_id + " had_memoized_value=" + + std::to_string(had_memoized_value) + + " memoized_value=" + memoized_value); + if (!had_memoized_value) { + has_memoized_value.store(true, std::memory_order_release); + return my_func_invocation_count; + } else { + EXPECT_EQ(my_func_invocation_count, 0); + return 0; + } + }(); + + // Make subsequent invocations of memoizer.value() and ensure that our + // local function is _not_ invoked, since we are guaranteed that a value + // was already memoized, either by us or by some other thread. + for (int j = 0; j < 100; j++) { + auto memoized_value = memoizer.value(func); + SCOPED_TRACE("thread i=" + thread_id + " j=" + std::to_string(j) + + " memoized_value=" + memoized_value); + EXPECT_EQ(my_func_invocation_count, expected_func_invocation_count); + } + }); } for (auto& thread : threads) { @@ -57,6 +154,387 @@ TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { } } -} // namespace util -} // namespace firestore -} // namespace firebase +TEST(ThreadSafeMemoizerTest, + CopyConstructor_NoMemoizedValue_OriginalMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest(memoizer); + + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); + + EXPECT_GT(memoizer_counter.invocation_count(), 0); + EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, + CopyConstructor_NoMemoizedValue_CopyMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest(memoizer); + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + + EXPECT_GT(memoizer_counter.invocation_count(), 0); + EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, CopyConstructor_MemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + ThreadSafeMemoizer memoizer_copy_dest(memoizer); + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); + + EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, MoveConstructor_NoMemoizedValue) { + CountingFunc memoizer_move_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_move_dest(std::move(memoizer)); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "bbb"); + + EXPECT_GT(memoizer_move_dest_counter.invocation_count(), 0); + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, MoveConstructor_MemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + ThreadSafeMemoizer memoizer_move_dest(std::move(memoizer)); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "aaa"); + + EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, + CopyAssignment_NoMemoizedValueToNoMemoizedValue_OriginalMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest; + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); +} + +TEST(ThreadSafeMemoizerTest, + CopyAssignment_NoMemoizedValueToNoMemoizedValue_CopyMemoizesFirst) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest; + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToNoMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + const auto expected_memoizer_counter_invocation_count = + memoizer_counter.invocation_count(); + ThreadSafeMemoizer memoizer_copy_dest; + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_EQ(memoizer_counter.invocation_count(), + expected_memoizer_counter_invocation_count); + EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_NoMemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter1("bbb1"), + memoizer_copy_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_copy_dest; + memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), + "bbb2"); + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter1("aaa1"), memoizer_counter2("aaa2"), + memoizer_copy_dest_counter1("bbb1"), memoizer_copy_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter1.func()); + const auto expected_memoizer_counter1_invocation_count = + memoizer_counter1.invocation_count(); + ThreadSafeMemoizer memoizer_copy_dest; + memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); + const auto expected_memoizer_copy_dest_counter1_invocation_count = + memoizer_copy_dest_counter1.invocation_count(); + + memoizer_copy_dest = memoizer; + + EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), + "aaa1"); + EXPECT_EQ(memoizer.value(memoizer_counter2.func()), "aaa1"); + EXPECT_EQ(memoizer_counter1.invocation_count(), + expected_memoizer_counter1_invocation_count); + EXPECT_EQ(memoizer_copy_dest_counter1.invocation_count(), + expected_memoizer_copy_dest_counter1_invocation_count); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_CopyToSelf_NoMemoizedValue) { + CountingFunc memoizer_counter("aaa"); + ThreadSafeMemoizer memoizer; + auto& looks_like_another_memoizer = memoizer; + ASSERT_EQ(&memoizer, &looks_like_another_memoizer); + + memoizer = looks_like_another_memoizer; + + EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); + EXPECT_GT(memoizer_counter.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_CopyToSelf_MemoizedValue) { + CountingFunc memoizer_counter("aaa_%s"); + auto func = memoizer_counter.func(); + ThreadSafeMemoizer memoizer; + auto& looks_like_another_memoizer = memoizer; + ASSERT_EQ(&memoizer, &looks_like_another_memoizer); + const auto memoized_value = memoizer.value(func); + const auto expected_invocation_count = memoizer_counter.invocation_count(); + + memoizer = looks_like_another_memoizer; + + EXPECT_EQ(memoizer.value(func), memoized_value); + EXPECT_EQ(memoizer_counter.invocation_count(), expected_invocation_count); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToNoMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter("bbb"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter.func()); + ThreadSafeMemoizer memoizer_move_dest; + + memoizer_move_dest = std::move(memoizer); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "aaa"); + EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 0); + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_NoMemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter("aaa"), memoizer_move_dest_counter1("bbb1"), + memoizer_move_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + ThreadSafeMemoizer memoizer_move_dest; + memoizer_move_dest.value(memoizer_move_dest_counter1.func()); + + memoizer_move_dest = std::move(memoizer); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), + "bbb2"); + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToMemoizedValue) { + CountingFunc memoizer_counter1("aaa1"), memoizer_counter2("aaa2"), + memoizer_move_dest_counter1("bbb1"), memoizer_move_dest_counter2("bbb2"); + ThreadSafeMemoizer memoizer; + memoizer.value(memoizer_counter1.func()); + const auto expected_memoizer_counter1_invocation_count = + memoizer_counter1.invocation_count(); + ThreadSafeMemoizer memoizer_move_dest; + memoizer_move_dest.value(memoizer_move_dest_counter1.func()); + const auto expected_memoizer_move_dest_counter1_invocation_count = + memoizer_move_dest_counter1.invocation_count(); + + memoizer_move_dest = std::move(memoizer); + + EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), + "aaa1"); + EXPECT_EQ(memoizer_counter1.invocation_count(), + expected_memoizer_counter1_invocation_count); + EXPECT_EQ(memoizer_move_dest_counter1.invocation_count(), + expected_memoizer_move_dest_counter1_invocation_count); + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_MoveToSelf_NoMemoizedValue) { + CountingFunc memoizer_counter("aaa"); + ThreadSafeMemoizer memoizer; + auto& looks_like_another_memoizer = memoizer; + ASSERT_EQ(&memoizer, &looks_like_another_memoizer); + + memoizer = std::move(looks_like_another_memoizer); + + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, MoveAssignment_MoveToSelf_MemoizedValue) { + CountingFunc memoizer_counter("aaa_%s"); + auto func = memoizer_counter.func(); + ThreadSafeMemoizer memoizer; + auto& looks_like_another_memoizer = memoizer; + ASSERT_EQ(&memoizer, &looks_like_another_memoizer); + memoizer.value(func); + + memoizer = std::move(looks_like_another_memoizer); + + VerifyWorksAfterBeingAssigned(memoizer); +} + +TEST(ThreadSafeMemoizerTest, + CopyConstructor_CopySourceKeepsMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + auto memoizer = std::make_unique>(); + memoizer->value([&] { return std::make_shared(destroyed); }); + + auto memoizer_copy_dest = + std::make_unique>(*memoizer); + + ASSERT_FALSE(destroyed.load()); + memoizer_copy_dest.reset(); + ASSERT_FALSE(destroyed.load()); + memoizer.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, CopyAssignment_CopySourceKeepsMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + auto memoizer = std::make_unique>(); + memoizer->value([&] { return std::make_shared(destroyed); }); + auto memoizer_copy_dest = + std::make_unique>(); + + *memoizer_copy_dest = *memoizer; + + ASSERT_FALSE(destroyed.load()); + memoizer_copy_dest.reset(); + ASSERT_FALSE(destroyed.load()); + memoizer.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, + MoveConstructor_MoveSourceDoesNotKeepMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + ThreadSafeMemoizer memoizer; + memoizer.value([&] { return std::make_shared(destroyed); }); + + auto memoizer_move_dest = + std::make_unique>( + std::move(memoizer)); + + ASSERT_FALSE(destroyed.load()); + memoizer_move_dest.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, + MoveAssignment_MoveSourceDoesNotKeepMemoizedValueAlive) { + CountingFunc memoizer_counter; + std::atomic destroyed{false}; + ThreadSafeMemoizer memoizer; + memoizer.value([&] { return std::make_shared(destroyed); }); + auto memoizer_move_dest = + std::make_unique>(); + + *memoizer_move_dest = std::move(memoizer); + + ASSERT_FALSE(destroyed.load()); + memoizer_move_dest.reset(); + ASSERT_TRUE(destroyed.load()); +} + +TEST(ThreadSafeMemoizerTest, TSAN_ConcurrentCallsToValueShouldNotDataRace) { + ThreadSafeMemoizer memoizer; + const auto num_threads = max_practical_parallel_threads_for_testing() * 4; + CountDownLatch latch(num_threads); + std::vector threads; + for (auto i = num_threads; i > 0; --i) { + threads.emplace_back([i, &latch, &memoizer] { + latch.arrive_and_wait(); + memoizer.value([i] { return std::make_shared(i); }); + }); + } + for (auto&& thread : threads) { + thread.join(); + } +} + +TEST(ThreadSafeMemoizerTest, TSAN_ValueInACopyShouldNotDataRace) { + ThreadSafeMemoizer memoizer; + memoizer.value([&] { return std::make_shared(1111); }); + std::unique_ptr> memoizer_copy; + // NOTE: Always use std::memory_order_relaxed when loading from and storing + // into this variable to avoid creating a happens-before relationship, which + // would defeat the purpose of this test. + std::atomic*> memoizer_copy_atomic(nullptr); + + std::thread thread1([&] { + memoizer_copy = std::make_unique>(memoizer); + memoizer_copy_atomic.store(memoizer_copy.get(), std::memory_order_relaxed); + }); + std::thread thread2([&] { + ThreadSafeMemoizer* memoizer_ptr = nullptr; + while (true) { + memoizer_ptr = memoizer_copy_atomic.load(std::memory_order_relaxed); + if (memoizer_ptr) { + break; + } + std::this_thread::yield(); + } + memoizer_ptr->value([&] { return std::make_shared(2222); }); + }); + + thread1.join(); + thread2.join(); + + const auto memoizer_copy_value = + memoizer_copy->value([&] { return std::make_shared(3333); }); + EXPECT_EQ(memoizer_copy_value, 1111); +} + +void VerifyWorksAfterBeingAssigned(ThreadSafeMemoizer& memoizer) { + ThreadSafeMemoizer memoizer2; + CountingFunc counter2("sx22pz64dn_%s"); + auto func2 = counter2.func(); + const bool counter2_had_memoized_value = GenerateRandomBool(); + + // Randomly select whether the original memoizer had a memoized value. + const std::string memoized_value = counter2_had_memoized_value + ? memoizer2.value(func2) + : "(error code nnwyh34mtx)"; + const auto invocation_count_before = counter2.invocation_count(); + + // Randomly select copy-assignment or move-assignment. + if (GenerateRandomBool()) { + memoizer = memoizer2; + } else { + memoizer = std::move(memoizer2); + } + + if (counter2_had_memoized_value) { + EXPECT_EQ(memoizer.value(func2), memoized_value); + EXPECT_EQ(counter2.invocation_count(), invocation_count_before); + } else { + CountingFunc counter3("mx3rfb8qqk"); + EXPECT_EQ(memoizer.value(counter3.func()), "mx3rfb8qqk"); + EXPECT_EQ(counter2.invocation_count(), invocation_count_before); + } +} + +} // namespace diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc new file mode 100644 index 00000000000..5e127bb7ec6 --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -0,0 +1,125 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace firebase { +namespace firestore { +namespace testing { +namespace { + +std::vector SplitSeparators(const std::string& s) { + std::vector chunks; + + auto found = s.find('%'); + decltype(found) search_start = 0; + decltype(found) substr_start = 0; + while (found != std::string::npos && found < s.size() - 1) { + const auto next_char = s[found + 1]; + if (next_char == 's' || next_char == 'c') { + chunks.push_back(s.substr(substr_start, found - substr_start)); + chunks.push_back(s.substr(found, 2)); + search_start = found + 2; + substr_start = search_start; + } else { + search_start = found + 1; + } + found = s.find('%', search_start); + } + chunks.push_back(s.substr(substr_start)); + + return chunks; +} + +} // namespace + +CountingFunc::CountingFunc(const std::string& format) + : CountingFunc(SplitSeparators(format)) { +} + +CountingFunc::CountingFunc(std::vector chunks) + : chunks_(std::move(chunks)) { + assert(!chunks_.empty()); + // Explicitly store the initial value into count_ because initialization of + // std::atomic is _not_ atomic. + count_.store(0); +} + +std::function()> CountingFunc::func( + std::string cookie) { + return [this, cookie = std::move(cookie)] { + return std::make_shared(Next(cookie)); + }; +} + +int CountingFunc::invocation_count() const { + return count_.load(std::memory_order_acquire); +} + +std::string CountingFunc::Next(const std::string& cookie) { + const int id = count_.fetch_add(1, std::memory_order_acq_rel); + std::ostringstream ss; + for (const std::string& chunk : chunks_) { + if (chunk == "%s") { + ss << id; + } else if (chunk == "%c" && cookie.size() > 0) { + ss << cookie; + } else { + ss << chunk; + } + } + return ss.str(); +} + +CountDownLatch::CountDownLatch(int count) { + // Explicitly store the count into the atomic because initialization is + // NOT atomic. + count_.store(count); +} + +void CountDownLatch::arrive_and_wait() { + count_.fetch_sub(1); + while (count_.load() > 0) { + std::this_thread::yield(); + } +} + +decltype(std::thread::hardware_concurrency()) +max_practical_parallel_threads_for_testing() { + const auto hardware_concurrency = std::thread::hardware_concurrency(); + return hardware_concurrency != 0 ? hardware_concurrency : 4; +} + +bool GenerateRandomBool() { + std::random_device random_device; + std::default_random_engine random_engine(random_device()); + const auto random_value = random_engine.operator()(); + return random_value % 2 == 0; +} + +} // namespace testing +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h new file mode 100644 index 00000000000..06acf58e8e4 --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -0,0 +1,145 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ +#define FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace testing { + +#if defined(GTEST_USES_SIMPLE_RE) || defined(GTEST_USES_RE2) +constexpr const char* FST_RE_DIGIT = "\\d"; +#elif defined(GTEST_USES_POSIX_RE) +constexpr const char* FST_RE_DIGIT = "[[:digit:]]"; +#endif + +/** + * Generates strings that incorporate a count in a thread-safe manner. + * + * The "format" string given to the constructor is literally generated, except + * that all occurrences of "%s" are replaced with the invocation count, and + * all occurrences of "%c" are replaced with the cookie, if a cookie is + * specified. + * + * All functions in this class may be safely called concurrently by multiple + * threads. + */ +class CountingFunc { + public: + /** + * Creates a new `CountingFunc` that generates strings that are equal to + * the base-10 string representation of the invocation count. + */ + CountingFunc() : CountingFunc("%s") { + } + + /** + * Creates a new `CountingFunc` that generates strings that match the given + * format. + * @param format the format to use when generating strings; all occurrences of + * "%s" will be replaced by the count, which starts at 0 (zero). + */ + explicit CountingFunc(const std::string& format); + + /** + * Returns a function that, when invoked, generates a string using the format + * given to the constructor. Every string returned by the function has a + * different count. + * + * Although each invocation of this function _may_ return a distinct function, + * they all use the same counter and may be safely called concurrently from + * multiple threads. + * + * The returned function is valid as long as this `CountingFunc` object is + * valid. + */ + std::function()> func() { + return func(""); + } + + std::function()> func(std::string cookie); + + /** + * Returns the total number of invocations that have occurred on functions + * returned by `func()`. A new instance of this class will return 0 (zero). + */ + int invocation_count() const; + + private: + std::atomic count_; + std::mutex mutex; + std::vector chunks_; + + explicit CountingFunc(std::vector chunks); + std::string Next(const std::string& cookie); +}; + +/** + * A simple implementation of std::latch in C++20. + */ +class CountDownLatch { + public: + explicit CountDownLatch(int count); + void arrive_and_wait(); + + private: + std::atomic count_; +}; + +class SetOnDestructor { + public: + explicit SetOnDestructor(std::atomic& flag) : flag_(flag) { + } + + ~SetOnDestructor() { + flag_.store(true); + } + + private: + std::atomic& flag_; +}; + +/** + * Returns the largest number of threads that can be truly executed in parallel, + * or an arbitrary value greater than one if the number of CPU cores cannot be + * determined. + */ +decltype(std::thread::hardware_concurrency()) +max_practical_parallel_threads_for_testing(); + +/** + * Generates and returns a random boolean value. + */ +bool GenerateRandomBool(); + +} // namespace testing +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_TEST_UNIT_UTIL_THREAD_SAFE_MEMOIZER_TESTING_H_ diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc new file mode 100644 index 00000000000..2f73d9c8c08 --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -0,0 +1,277 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +using firebase::firestore::testing::CountDownLatch; +using firebase::firestore::testing::CountingFunc; +using firebase::firestore::testing::max_practical_parallel_threads_for_testing; + +TEST(ThreadSafeMemoizerTesting, DefaultConstructor) { + CountingFunc counting_func; + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncShouldReturnSameStringIfNoReplacements) { + CountingFunc counting_func("tdjebqrtny"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + EXPECT_EQ(*func(), "tdjebqrtny"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesPercentSAtStart) { + CountingFunc counting_func("%scmgb5bsbj2"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str + "cmgb5bsbj2"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesPercentSAtEnd) { + CountingFunc counting_func("nd3krmj2mn%s"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "nd3krmj2mn" + i_str); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesPercentSInMiddle) { + CountingFunc counting_func("txxz4%sddrs5"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "txxz4" + i_str + "ddrs5"); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncHandlesMultiplePercentSReplacements) { + CountingFunc counting_func("%scx%s3b%s5jazwf%s"); + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str + "cx" + i_str + "3b" + i_str + "5jazwf" + i_str); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesPercentCAtStart) { + CountingFunc counting_func("%cwxxsz2qm2e"); + auto func = counting_func.func("7k4bek9pfx"); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + EXPECT_EQ(*func(), "7k4bek9pfxwxxsz2qm2e"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesPercentCAtEnd) { + CountingFunc counting_func("7432wt5hnw%c"); + auto func = counting_func.func("yzcjsrh5tp"); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + EXPECT_EQ(*func(), "7432wt5hnwyzcjsrh5tp"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesPercentCInMiddle) { + CountingFunc counting_func("wxxsz%c2qm2e"); + auto func = counting_func.func("gptebm6kh5"); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + EXPECT_EQ(*func(), "wxxszgptebm6kh52qm2e"); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncHandlesMultiplePercentCReplacements) { + CountingFunc counting_func("%cw7%c98%c8cg5mz%c"); + auto func = counting_func.func("ww3"); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); + EXPECT_EQ(*func(), "ww3w7ww398ww38cg5mzww3"); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncHandlesDifferingPercentCReplacements) { + CountingFunc counting_func("5c8sc_%c_gr7vf"); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + auto func = counting_func.func("a" + i_str + "a"); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "5c8sc_a" + i_str + "a_gr7vf"); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncHandlesAlternatingPercentReplacements1) { + CountingFunc counting_func("%s_%c_%s_%c_%s"); + auto func = counting_func.func("bbb"); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), i_str + "_bbb_" + i_str + "_bbb_" + i_str); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncHandlesAlternatingPercentReplacements2) { + CountingFunc counting_func("%c_%s_%c_%s_%c"); + auto func = counting_func.func("bbb"); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "bbb_" + i_str + "_bbb_" + i_str + "_bbb"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncHandlesInvalidPercents) { + CountingFunc counting_func("%%s %% %x %cs %"); + auto func = counting_func.func("zzz"); + for (int i = 0; i < 100; i++) { + const std::string i_str = std::to_string(i); + SCOPED_TRACE("iteration i=" + i_str); + EXPECT_EQ(*func(), "%" + i_str + " %% %x zzzs %"); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncFunctionsUseSameCounter) { + CountingFunc counting_func("3gswsz9hyd_%s"); + const std::vector funcs{ + counting_func.func(), counting_func.func(), counting_func.func(), + counting_func.func(), counting_func.func()}; + int next_id = 0; + for (int i = 0; i < 100; i++) { + for (decltype(funcs.size()) j = 0; j < funcs.size(); j++) { + SCOPED_TRACE("iteration i=" + std::to_string(i) + + " j=" + std::to_string(j)); + EXPECT_EQ(*funcs[j](), "3gswsz9hyd_" + std::to_string(next_id++)); + } + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncThreadSafety) { + CountingFunc counting_func("ejrxk3g6tb_%s"); + std::vector threads; + std::array, 20> strings; + CountDownLatch latch(strings.size()); + for (decltype(strings.size()) i = 0; i < strings.size(); i++) { + threads.emplace_back([&, i] { + auto func = counting_func.func(); + auto& results = strings[i]; + latch.arrive_and_wait(); + for (decltype(results.size()) j = 0; j < results.size(); j++) { + results[j] = *func(); + } + }); + } + + for (auto& thread : threads) { + thread.join(); + } + + std::vector actual_strings; + for (const auto& thread_strings : strings) { + actual_strings.insert(actual_strings.end(), thread_strings.begin(), + thread_strings.end()); + } + + std::vector expected_strings; + for (decltype(actual_strings.size()) i = 0; i < actual_strings.size(); i++) { + expected_strings.push_back("ejrxk3g6tb_" + std::to_string(i)); + } + + std::sort(actual_strings.begin(), actual_strings.end()); + std::sort(expected_strings.begin(), expected_strings.end()); + ASSERT_EQ(actual_strings, expected_strings); +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountOnNewInstance) { + CountingFunc counting_func; + EXPECT_EQ(counting_func.invocation_count(), 0); +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountIncrementsBy1) { + CountingFunc counting_func; + auto func = counting_func.func(); + for (int i = 0; i < 100; i++) { + EXPECT_EQ(counting_func.invocation_count(), i); + func(); + EXPECT_EQ(counting_func.invocation_count(), i + 1); + } +} + +TEST(ThreadSafeMemoizerTesting, + CountingFuncInvocationCountIncrementedByEachFunc) { + CountingFunc counting_func; + for (int i = 0; i < 100; i++) { + auto func = counting_func.func(); + EXPECT_EQ(counting_func.invocation_count(), i); + func(); + EXPECT_EQ(counting_func.invocation_count(), i + 1); + } +} + +TEST(ThreadSafeMemoizerTesting, CountingFuncInvocationCountThreadSafe) { + CountingFunc counting_func; + const int num_threads = max_practical_parallel_threads_for_testing(); + std::vector threads; + CountDownLatch latch(num_threads); + for (auto i = num_threads; i > 0; i--) { + threads.emplace_back([&, i] { + auto func = counting_func.func(); + latch.arrive_and_wait(); + auto last_count = counting_func.invocation_count(); + for (int j = 0; j < 100; j++) { + SCOPED_TRACE("Thread i=" + std::to_string(i) + + " j=" + std::to_string(j)); + func(); + auto new_count = counting_func.invocation_count(); + EXPECT_GT(new_count, last_count); + last_count = new_count; + } + }); + } + + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_EQ(counting_func.invocation_count(), num_threads * 100); +} + +} // namespace From 634a47a98dbc7d0bbdb06deac6f8d2c0878d2810 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 15:45:29 -0500 Subject: [PATCH 76/84] Firestore/Example/Firestore.xcodeproj/project.pbxproj updated --- .../Firestore.xcodeproj/project.pbxproj | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 7b6e8450bf1..15abfd2adfb 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -259,6 +259,7 @@ 258B372CF33B7E7984BBA659 /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; 25C167BAA4284FC951206E1F /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; + 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 25FE27330996A59F31713A0C /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; }; 2618255E63631038B64DF3BB /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 2620644052E960310DADB298 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; @@ -400,6 +401,7 @@ 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; + 3D6AC48D6197E6539BBBD28F /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; 3DBB48F077C97200F32B51A0 /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 3DBBC644BE08B140BCC23BD5 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; @@ -432,6 +434,7 @@ 44A8B51C05538A8DACB85578 /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44C4244E42FFFB6E9D7F28BA /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44EAF3E6EAC0CC4EB2147D16 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; + 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 4562CDD90F5FF0491F07C5DA /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; 457171CE2510EEA46F7D8A30 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; @@ -452,6 +455,7 @@ 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; 47B8ED6737A24EF96B1ED318 /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; }; 4809D7ACAA9414E3192F04FF /* FIRGeoPointTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */; }; + 482D503CC826265FCEAB53DE /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 485CBA9F99771437BA1CB401 /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 48720B5768AFA2B2F3E14C04 /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 48926FF55484E996B474D32F /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = DD990FD89C165F4064B4F608 /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json */; }; @@ -783,6 +787,7 @@ 67B8C34BDF0FFD7532D7BE4F /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */; }; 67BC2B77C1CC47388E79D774 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; }; 67CF9FAA890307780731E1DA /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; + 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 6938575C8B5E6FE0D562547A /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; 6938ABD1891AD4B9FD5FE664 /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; }; 69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; }; @@ -873,6 +878,7 @@ 77C5703230DB77F0540D1F89 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */; }; 77D38E78F7CCB8504450A8FB /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; 77D3CF0BE43BC67B9A26B06D /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; + 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 784FCB02C76096DACCBA11F2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 78D99CDBB539B0AEE0029831 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 3841925AA60E13A027F565E6 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json */; }; 78E8DDDBE131F3DA9AF9F8B8 /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -991,6 +997,7 @@ 8C602DAD4E8296AB5EFB962A /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */; }; 8D0EF43F1B7B156550E65C20 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; + 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 8DBA8DC55722ED9D3A1BB2C9 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 1A7D48A017ECB54FD381D126 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json */; }; 8E103A426D6E650DC338F281 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; 8E41D53C77C30372840B0367 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 728F617782600536F2561463 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json */; }; @@ -1114,6 +1121,7 @@ A728A4D7FA17F9F3257E0002 /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8582DFD74E8060C7072104B /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json */; }; A7309DAD4A3B5334536ECA46 /* remote_event_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 584AE2C37A55B408541A6FF3 /* remote_event_test.cc */; }; A7399FB3BEC50BBFF08EC9BA /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; + A7669E72BCED7FBADA4B1314 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; A80D38096052F928B17E1504 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; A833A216988ADFD4876763CD /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; A841EEB5A94A271523EAE459 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = A5D9044B72061CAF284BC9E4 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json */; }; @@ -1268,6 +1276,7 @@ BC8DFBCB023DBD914E27AA7D /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; BCA720A0F54D23654F806323 /* ConditionalConformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3228F51DCDC2E90D5C58F97 /* ConditionalConformanceTests.swift */; }; BCAC9F7A865BD2320A4D8752 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; + BD0882A40BD8AE042629C179 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; BD3A421C9E40C57D25697E75 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BD051DBE754950FEAC7A446 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json */; }; BD6CC8614970A3D7D2CF0D49 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; BDD2D1812BAD962E3C81A53F /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; @@ -1284,6 +1293,7 @@ BFEAC4151D3AA8CE1F92CC2D /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; C02A969BF4BB63ABCB531B4B /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */; }; C06E54352661FCFB91968640 /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; + C099AEC05D44976755BA32A2 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; C09BDBA73261578F9DA74CEE /* firebase_auth_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = F869D85E900E5AF6CD02E2FC /* firebase_auth_credentials_provider_test.mm */; }; C0AD8DB5A84CAAEE36230899 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; C0EFC5FB79517679C377C252 /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; @@ -1358,6 +1368,7 @@ CE2962775B42BDEEE8108567 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; CE411D4B70353823DE63C0D5 /* bundle_loader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A853C81A6A5A51C9D0389EDA /* bundle_loader_test.cc */; }; CEA91CE103B42533C54DBAD6 /* memory_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */; }; + CF18D52A88F4F6F62C5495EF /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; CF1FB026CCB901F92B4B2C73 /* watch_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2D7472BC70C024D736FF74D9 /* watch_change_test.cc */; }; CF5DE1ED21DD0A9783383A35 /* CodableIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124C932B22C1642C00CA8C2D /* CodableIntegrationTests.swift */; }; CFA4A635ECD105D2044B3692 /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3355BE9391CC4857AF0BDAE3 /* DatabaseTests.swift */; }; @@ -1409,6 +1420,7 @@ D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; + D928302820891CCCAD0437DD /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; D94A1862B8FB778225DB54A1 /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; D98430EA4FAA357D855FA50F /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; @@ -1725,6 +1737,7 @@ 214877F52A705012D6720CA0 /* object_value_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = object_value_test.cc; sourceTree = ""; }; 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_target_cache_test.cc; sourceTree = ""; }; + 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = thread_safe_memoizer_testing.h; sourceTree = ""; }; 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = lru_garbage_collector_test.cc; sourceTree = ""; }; 28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = logic_utils_test.cc; sourceTree = ""; }; 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_document_overlay_cache_test.cc; sourceTree = ""; }; @@ -1932,6 +1945,7 @@ 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = ""; }; 6A7A30A2DB3367E08939E789 /* bloom_filter.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = bloom_filter.pb.h; sourceTree = ""; }; 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.release.xcconfig"; sourceTree = ""; }; + 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_testing.cc; sourceTree = ""; }; 6E8302DE210222ED003E1EA3 /* FSTFuzzTestFieldPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTFuzzTestFieldPath.h; sourceTree = ""; }; 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestFieldPath.mm; sourceTree = ""; }; 6EA39FDD20FE820E008D461F /* FSTFuzzTestSerializer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestSerializer.mm; sourceTree = ""; }; @@ -2109,6 +2123,7 @@ E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = ""; }; E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_target_cache_test.cc; sourceTree = ""; }; + EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_testing_test.cc; sourceTree = ""; }; ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EF3A65472C66B9560041EE69 /* FIRVectorValueTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRVectorValueTests.mm; sourceTree = ""; }; EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRAggregateTests.mm; sourceTree = ""; }; @@ -2412,6 +2427,9 @@ 899FC22684B0F7BEEAE13527 /* task_test.cc */, A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */, 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */, + 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */, + 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */, + EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */, B68B1E002213A764008977EF /* to_string_apple_test.mm */, B696858D2214B53900271095 /* to_string_test.cc */, ); @@ -4327,6 +4345,8 @@ 9B2C6A48A4DBD36080932B4E /* testing_hooks_test.cc in Sources */, 32A95242C56A1A230231DB6A /* testutil.cc in Sources */, 51018EA27CF914DD1CC79CB3 /* thread_safe_memoizer_test.cc in Sources */, + 482D503CC826265FCEAB53DE /* thread_safe_memoizer_testing.cc in Sources */, + 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB78229DECDE000FB92F /* time_testing.cc in Sources */, ACC9369843F5ED3BD2284078 /* timestamp_test.cc in Sources */, 2AAEABFD550255271E3BAC91 /* to_string_apple_test.mm in Sources */, @@ -4548,6 +4568,8 @@ 24B75C63BDCD5551B2F69901 /* testing_hooks_test.cc in Sources */, 8388418F43042605FB9BFB92 /* testutil.cc in Sources */, 5BB33F0BC7960D26062B07D3 /* thread_safe_memoizer_test.cc in Sources */, + 3D6AC48D6197E6539BBBD28F /* thread_safe_memoizer_testing.cc in Sources */, + 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB79229DECDE000FB92F /* time_testing.cc in Sources */, 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */, 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */, @@ -4793,6 +4815,8 @@ D0DA42DC66C4FE508A63B269 /* testing_hooks_test.cc in Sources */, 409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */, B31B5E0D4EA72C5916CC71F5 /* thread_safe_memoizer_test.cc in Sources */, + 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */, + 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */, 6300709ECDE8E0B5A8645F8D /* time_testing.cc in Sources */, 0CEE93636BA4852D3C5EC428 /* timestamp_test.cc in Sources */, 95DCD082374F871A86EF905F /* to_string_apple_test.mm in Sources */, @@ -5038,6 +5062,8 @@ F6738D3B72352BBEFB87172C /* testing_hooks_test.cc in Sources */, A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */, 09B83B26E47B6F6668DF54B8 /* thread_safe_memoizer_test.cc in Sources */, + CF18D52A88F4F6F62C5495EF /* thread_safe_memoizer_testing.cc in Sources */, + A7669E72BCED7FBADA4B1314 /* thread_safe_memoizer_testing_test.cc in Sources */, A25FF76DEF542E01A2DF3B0E /* time_testing.cc in Sources */, 1E42CD0F60EB22A5D0C86D1F /* timestamp_test.cc in Sources */, F9705E595FC3818F13F6375A /* to_string_apple_test.mm in Sources */, @@ -5269,6 +5295,8 @@ F184E5367DF3CA158EDE8532 /* testing_hooks_test.cc in Sources */, 54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */, 20A93AC59CD5A7AC41F10412 /* thread_safe_memoizer_test.cc in Sources */, + 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */, + BD0882A40BD8AE042629C179 /* thread_safe_memoizer_testing_test.cc in Sources */, 5497CB77229DECDE000FB92F /* time_testing.cc in Sources */, ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */, B68B1E012213A765008977EF /* to_string_apple_test.mm in Sources */, @@ -5533,6 +5561,8 @@ 5360D52DCAD1069B1E4B0B9D /* testing_hooks_test.cc in Sources */, CA989C0E6020C372A62B7062 /* testutil.cc in Sources */, 6DFD49CCE2281CE243FEBB63 /* thread_safe_memoizer_test.cc in Sources */, + D928302820891CCCAD0437DD /* thread_safe_memoizer_testing.cc in Sources */, + C099AEC05D44976755BA32A2 /* thread_safe_memoizer_testing_test.cc in Sources */, 2D220B9ABFA36CD7AC43D0A7 /* time_testing.cc in Sources */, D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */, 60260A06871DCB1A5F3448D3 /* to_string_apple_test.mm in Sources */, From 8f6548800d3844a9b7eb49e5d697f5aebce568e3 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 15:49:54 -0500 Subject: [PATCH 77/84] scripts/cpplint.py: remove warnings about using std::make_unique() since it is part of the standard library now that C++14 is the min-supported C++ version. --- scripts/cpplint.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 07e397599e2..20025d33a46 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -6390,16 +6390,6 @@ def FlagCxx14Features(filename, clean_lines, linenum, error): error(filename, linenum, 'build/c++14', 5, ('<%s> is an unapproved C++14 header.') % include.group(1)) - # These are classes and free functions with abseil equivalents. - for top_name in ( - # memory - 'make_unique', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++14', 5, - 'std::%s does not exist in C++11. Use absl::%s instead.' % - (top_name, top_name)) - def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=None): From 11b532498eef7abbe658a3dede61128a199e1ce8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 15:56:32 -0500 Subject: [PATCH 78/84] fix lint --- Firestore/core/test/unit/util/thread_safe_memoizer_test.cc | 3 ++- Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc | 2 +- Firestore/core/test/unit/util/thread_safe_memoizer_testing.h | 4 ++-- .../core/test/unit/util/thread_safe_memoizer_testing_test.cc | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 2c5d57c1116..2e8169d5ea4 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -18,7 +18,7 @@ #include #include -#include +#include // NOLINT(build/c++11) #include #include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" @@ -27,6 +27,7 @@ namespace { +// NOLINTNEXTLINE(build/namespaces_literals) using namespace std::literals::string_literals; using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index 5e127bb7ec6..a6b80964f77 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -22,7 +22,7 @@ #include #include #include -#include +#include // NOLINT(build/c++11) #include #include diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index 06acf58e8e4..2595416fed5 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -22,9 +22,9 @@ #include #include #include -#include +#include // NOLINT(build/c++11) #include -#include +#include // NOLINT(build/c++11) #include #include "gtest/gtest.h" diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc index 2f73d9c8c08..6123af107c2 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -18,7 +18,7 @@ #include #include -#include +#include // NOLINT(build/c++11) #include "gtest/gtest.h" From a209bd4575fda4f0f40bdfc0e69ebb65acb57229 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 15:59:20 -0500 Subject: [PATCH 79/84] scripts/check.sh: remove my changes since I'm not entirely convinced they're doing what I think they're doing. --- scripts/check.sh | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/scripts/check.sh b/scripts/check.sh index 76184979d98..59cad32aa93 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -293,21 +293,8 @@ python --version "${top_dir}/scripts/check_imports.swift" # Google C++ style - -# If cpplint.py itself changed, then run it on the entire repo. -if [[ "$GITHUB_EVENT_NAME" != pull_request ]] ; then - CHECK_LINT_CHANGED=0 -elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/check_lint.py') ]]; then - CHECK_LINT_CHANGED=1 -elif [[ -n $(git diff --name-only "$START_SHA" | grep 'scripts/cpplint.py') ]]; then - CHECK_LINT_CHANGED=1 -else - CHECK_LINT_CHANGED=0 -fi - lint_cmd=("${top_dir}/scripts/check_lint.py") -if [[ "$CHECK_DIFF" == true ]] && [[ "$CHECK_LINT_CHANGED" == 0 ]] ; then +if [[ "$CHECK_DIFF" == true ]]; then lint_cmd+=("${START_SHA}") fi - "${lint_cmd[@]}" From 6a5c6002a10bb0e7eebaec365931d206acfb5efe Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 16:00:19 -0500 Subject: [PATCH 80/84] scripts/cpplint.py: add back build/namespaces_literals so that cpplint.py _ignores_ (rather than fails) when this lint warning is suppressed. --- scripts/cpplint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 52010c3883c..87114b951a1 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -283,6 +283,7 @@ 'build/include_order', 'build/include_what_you_use', 'build/namespaces_headers', + 'build/namespaces_literals', 'build/namespaces', 'build/printf_format', 'build/storage_class', From f6e95a31d9092a96ce28bd06b553d71b31f17361 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 16:03:14 -0500 Subject: [PATCH 81/84] scripts/cpplint.py: undo local changes --- scripts/cpplint.py | 93 ++++------------------------------------------ 1 file changed, 7 insertions(+), 86 deletions(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 20025d33a46..87114b951a1 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -35,7 +35,6 @@ import math # for log import os import re -import sre_compile import string import sys import sysconfig @@ -1028,7 +1027,7 @@ def Match(pattern, s): # performance reasons; factoring it out into a separate function turns out # to be noticeably expensive. if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].match(s) @@ -1046,14 +1045,14 @@ def ReplaceAll(pattern, rep, s): string with replacements made (or original string if no replacements) """ if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].sub(rep, s) def Search(pattern, s): """Searches the string for the pattern, caching the compiled regexp.""" if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + _regexp_compile_cache[pattern] = re.compile(pattern) return _regexp_compile_cache[pattern].search(s) @@ -5356,15 +5355,10 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, 'Did you mean "memset(%s, 0, %s)"?' % (match.group(1), match.group(2))) - if Search(r'\busing namespace\b', line): - if Search(r'\bliterals\b', line): - error(filename, linenum, 'build/namespaces_literals', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') - else: - error(filename, linenum, 'build/namespaces', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') + if Search(r'\busing namespace\b', line) and not Search(r'\b::\w+_literals\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') # Detect variable-length arrays. match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) @@ -6320,77 +6314,6 @@ def ProcessLine(filename, file_extension, clean_lines, line, for check_fn in extra_check_functions: check_fn(filename, clean_lines, line, error) -def FlagCxx11Features(filename, clean_lines, linenum, error): - """Flag those c++11 features that we only allow in certain places. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++ TR1 headers. - if include and include.group(1).startswith('tr1/'): - error(filename, linenum, 'build/c++tr1', 5, - ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) - - # Flag unapproved C++11 headers. - if include and include.group(1) in ('cfenv', - 'condition_variable', - 'fenv.h', - 'future', - 'mutex', - 'thread', - 'chrono', - 'ratio', - 'regex', - 'system_error', - ): - error(filename, linenum, 'build/c++11', 5, - ('<%s> is an unapproved C++11 header.') % include.group(1)) - - # The only place where we need to worry about C++11 keywords and library - # features in preprocessor directives is in macro definitions. - if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return - - # These are classes and free functions. The classes are always - # mentioned as std::*, but we only catch the free functions if - # they're not found by ADL. They're alphabetical by header. - for top_name in ( - # type_traits - 'alignment_of', - 'aligned_union', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++11', 5, - ('std::%s is an unapproved C++11 class or function. Send c-style ' - 'an example of where it would make your code more readable, and ' - 'they may let you use it.') % top_name) - - -def FlagCxx14Features(filename, clean_lines, linenum, error): - """Flag those C++14 features that we restrict. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++14 headers. - if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): - error(filename, linenum, 'build/c++14', 5, - ('<%s> is an unapproved C++14 header.') % include.group(1)) - - def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=None): """Performs lint checks and reports any errors to the given error function. @@ -6427,8 +6350,6 @@ def ProcessFileData(filename, file_extension, lines, error, ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, extra_check_functions) - FlagCxx11Features(filename, clean_lines, line, error) - FlagCxx14Features(filename, clean_lines, line, error) nesting_state.CheckCompletedBlocks(filename, error) CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) From 605bcf48d5ff428f7248c9fb6205aedf3c1b61a9 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 13 Jan 2025 16:15:13 -0500 Subject: [PATCH 82/84] Revert "fix lint" This reverts commit 11b532498eef7abbe658a3dede61128a199e1ce8. --- Firestore/core/test/unit/util/thread_safe_memoizer_test.cc | 3 +-- Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc | 2 +- Firestore/core/test/unit/util/thread_safe_memoizer_testing.h | 4 ++-- .../core/test/unit/util/thread_safe_memoizer_testing_test.cc | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 2e8169d5ea4..2c5d57c1116 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -18,7 +18,7 @@ #include #include -#include // NOLINT(build/c++11) +#include #include #include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" @@ -27,7 +27,6 @@ namespace { -// NOLINTNEXTLINE(build/namespaces_literals) using namespace std::literals::string_literals; using firebase::firestore::testing::CountDownLatch; using firebase::firestore::testing::CountingFunc; diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index a6b80964f77..5e127bb7ec6 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -22,7 +22,7 @@ #include #include #include -#include // NOLINT(build/c++11) +#include #include #include diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h index 2595416fed5..06acf58e8e4 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.h @@ -22,9 +22,9 @@ #include #include #include -#include // NOLINT(build/c++11) +#include #include -#include // NOLINT(build/c++11) +#include #include #include "gtest/gtest.h" diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc index 6123af107c2..2f73d9c8c08 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing_test.cc @@ -18,7 +18,7 @@ #include #include -#include // NOLINT(build/c++11) +#include #include "gtest/gtest.h" From fae4442353640c6bfba7bcfd78ad05013bbc9a6d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 14 Jan 2025 16:57:24 +0000 Subject: [PATCH 83/84] thread_safe_memoizer.h: tighten determinism of value() to call the given function _at most once_, rather than _any number of times_. --- .../core/src/util/thread_safe_memoizer.h | 33 +++-- .../unit/util/thread_safe_memoizer_test.cc | 125 +++++++++--------- .../unit/util/thread_safe_memoizer_testing.cc | 8 +- 3 files changed, 88 insertions(+), 78 deletions(-) diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h index f99497f8472..18526031a01 100644 --- a/Firestore/core/src/util/thread_safe_memoizer.h +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -21,6 +21,8 @@ #include #include +#include "Firestore/core/src/util/hard_assert.h" + namespace firebase { namespace firestore { namespace util { @@ -100,28 +102,43 @@ class ThreadSafeMemoizer { * On the other hand, if this object does _not_ have a memoized value then * the given function is called to calculate the value to memoize. The value * returned by the function is stored internally as the "memoized value" and - * then returned. + * then returned. If multiple threads race in calls to this function then + * more than one of them may have their functions called but only one of them + * will be memoized, with the others being discarded. + * + * The given function will be called synchronously by this function either + * zero times or one time. No reference to the given function is retained by + * this object. * - * The given function *must* be idempotent because it _may_ be called more - * than once due to the semantics of "weak" compare-and-exchange. No reference - * to the given function is retained by this object. The given function will - * be called synchronously by this function, if it is called at all. + * The given function _must_ return an initialized `std::shared_ptr`; that + * is, the returned `std::shared_ptr` _must_ evaluate to `true` when + * converted to `bool`. It is undefined behavior if the returned + * `std::shared_ptr` does _not_ satisfy this requirement. * * This function is thread-safe and may be called concurrently by multiple * threads. * - * The returned reference should only be considered "valid" as long as this - * ThreadSafeMemoizer instance is alive. + * The returned reference is "valid" only as long as this `ThreadSafeMemoizer` + * object is alive; namely, once this `ThreadSafeMemoizer` object's destructor + * starts running, the reference returned by this function is invalid and + * using it is undefined behavior. */ const T& value(const std::function()>& func) { std::shared_ptr old_memoized = std::atomic_load(&memoized_); + std::shared_ptr new_memoized; + bool new_memoized_is_initialized = false; + while (true) { if (old_memoized) { return *old_memoized; } - std::shared_ptr new_memoized = func(); + if (!new_memoized_is_initialized) { + new_memoized = func(); + new_memoized_is_initialized = true; + HARD_ASSERT(new_memoized); + } if (std::atomic_compare_exchange_weak(&memoized_, &old_memoized, new_memoized)) { diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index 2c5d57c1116..e48faf654b0 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -41,9 +41,9 @@ using testing::StartsWith; /** * Performs a copy or move assignment (chosen randomly) on the given memoizer * and then ensure that it behaves as expected. This is useful for testing the - * "move" logic because a move-from object, according to the C++ standard, is in - * a "valid, but unspecified" state and the only operations it is guaranteed to - * support are assignment and destruction. + * "move" logic because a moved-from object, according to the C++ standard, is + * in a "valid, but unspecified" state and the only operations it is guaranteed + * to support are assignment and destruction. */ void VerifyWorksAfterBeingAssigned(ThreadSafeMemoizer& memoizer); @@ -65,16 +65,12 @@ TEST(ThreadSafeMemoizerTest, CountingFunc counter("tfj6v4kdxn_%s"); auto func = counter.func(); - const auto expected = memoizer.value(func); - // Do not hardcode "tfj6v4kdxn_0" as the expected value because - // ThreadSafeMemoizer.value() documents that it _may_ call the given function - // multiple times. - ASSERT_THAT(memoizer.value(func), - MatchesRegex("tfj6v4kdxn_"s + FST_RE_DIGIT + "+")); + memoizer.value(func); + ASSERT_EQ(memoizer.value(func), "tfj6v4kdxn_0"); for (int i = 0; i < 100; i++) { SCOPED_TRACE("iteration i=" + std::to_string(i)); - ASSERT_EQ(memoizer.value(func), expected); + ASSERT_EQ(memoizer.value(func), "tfj6v4kdxn_0"); } } @@ -83,14 +79,12 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldOnlyInvokeFunctionOnFirstInvocation) { CountingFunc counter; auto func = counter.func(); memoizer.value(func); - // Do not hardcode 1 as the expected invocation count because - // ThreadSafeMemoizer.value() documents that it _may_ call the given function - // multiple times. - const auto expected_invocation_count = counter.invocation_count(); + for (int i = 0; i < 100; i++) { + SCOPED_TRACE("iteration i=" + std::to_string(i)); memoizer.value(func); + EXPECT_EQ(counter.invocation_count(), 1); } - EXPECT_EQ(counter.invocation_count(), expected_invocation_count); } TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { @@ -116,6 +110,13 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { // maximize concurrent access to the ThreadSafeMemoizer object. latch.arrive_and_wait(); + // Give all the threads a chance to exit the busy wait in + // arrive_and_wait() to reduce the chance of unintended "happens-before" + // relationships between threads being established. + for (auto k = num_threads * 2; k > 0; --k) { + std::this_thread::yield(); + } + // Make an initial invocation of memoizer.value(). If some other thread // is known to have already set the memoized value then ensure that our // local function is _not_ invoked; otherwise, announce to the other @@ -128,12 +129,14 @@ TEST(ThreadSafeMemoizerTest, Value_ShouldNotInvokeTheFunctionAfterMemoizing) { SCOPED_TRACE("thread i=" + thread_id + " had_memoized_value=" + std::to_string(had_memoized_value) + " memoized_value=" + memoized_value); - if (!had_memoized_value) { - has_memoized_value.store(true, std::memory_order_release); - return my_func_invocation_count; - } else { + if (had_memoized_value) { EXPECT_EQ(my_func_invocation_count, 0); return 0; + } else { + has_memoized_value.store(true, std::memory_order_release); + EXPECT_GE(my_func_invocation_count, 0); + EXPECT_LE(my_func_invocation_count, 1); + return my_func_invocation_count; } }(); @@ -163,8 +166,8 @@ TEST(ThreadSafeMemoizerTest, EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); - EXPECT_GT(memoizer_counter.invocation_count(), 0); - EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); + EXPECT_EQ(memoizer_counter.invocation_count(), 1); + EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 1); } TEST(ThreadSafeMemoizerTest, @@ -176,8 +179,8 @@ TEST(ThreadSafeMemoizerTest, EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "bbb"); EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_GT(memoizer_counter.invocation_count(), 0); - EXPECT_GT(memoizer_copy_dest_counter.invocation_count(), 0); + EXPECT_EQ(memoizer_counter.invocation_count(), 1); + EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 1); } TEST(ThreadSafeMemoizerTest, CopyConstructor_MemoizedValue) { @@ -198,7 +201,7 @@ TEST(ThreadSafeMemoizerTest, MoveConstructor_NoMemoizedValue) { EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter.func()), "bbb"); - EXPECT_GT(memoizer_move_dest_counter.invocation_count(), 0); + EXPECT_EQ(memoizer_move_dest_counter.invocation_count(), 1); VerifyWorksAfterBeingAssigned(memoizer); } @@ -242,16 +245,13 @@ TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToNoMemoizedValue) { CountingFunc memoizer_counter("aaa"), memoizer_copy_dest_counter("bbb"); ThreadSafeMemoizer memoizer; memoizer.value(memoizer_counter.func()); - const auto expected_memoizer_counter_invocation_count = - memoizer_counter.invocation_count(); ThreadSafeMemoizer memoizer_copy_dest; memoizer_copy_dest = memoizer; EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter.func()), "aaa"); EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_EQ(memoizer_counter.invocation_count(), - expected_memoizer_counter_invocation_count); + EXPECT_EQ(memoizer_counter.invocation_count(), 1); EXPECT_EQ(memoizer_copy_dest_counter.invocation_count(), 0); } @@ -274,22 +274,16 @@ TEST(ThreadSafeMemoizerTest, CopyAssignment_MemoizedValueToMemoizedValue) { memoizer_copy_dest_counter1("bbb1"), memoizer_copy_dest_counter2("bbb2"); ThreadSafeMemoizer memoizer; memoizer.value(memoizer_counter1.func()); - const auto expected_memoizer_counter1_invocation_count = - memoizer_counter1.invocation_count(); ThreadSafeMemoizer memoizer_copy_dest; memoizer_copy_dest.value(memoizer_copy_dest_counter1.func()); - const auto expected_memoizer_copy_dest_counter1_invocation_count = - memoizer_copy_dest_counter1.invocation_count(); memoizer_copy_dest = memoizer; EXPECT_EQ(memoizer_copy_dest.value(memoizer_copy_dest_counter2.func()), "aaa1"); EXPECT_EQ(memoizer.value(memoizer_counter2.func()), "aaa1"); - EXPECT_EQ(memoizer_counter1.invocation_count(), - expected_memoizer_counter1_invocation_count); - EXPECT_EQ(memoizer_copy_dest_counter1.invocation_count(), - expected_memoizer_copy_dest_counter1_invocation_count); + EXPECT_EQ(memoizer_counter1.invocation_count(), 1); + EXPECT_EQ(memoizer_copy_dest_counter1.invocation_count(), 1); } TEST(ThreadSafeMemoizerTest, CopyAssignment_CopyToSelf_NoMemoizedValue) { @@ -301,7 +295,7 @@ TEST(ThreadSafeMemoizerTest, CopyAssignment_CopyToSelf_NoMemoizedValue) { memoizer = looks_like_another_memoizer; EXPECT_EQ(memoizer.value(memoizer_counter.func()), "aaa"); - EXPECT_GT(memoizer_counter.invocation_count(), 0); + EXPECT_EQ(memoizer_counter.invocation_count(), 1); } TEST(ThreadSafeMemoizerTest, CopyAssignment_CopyToSelf_MemoizedValue) { @@ -310,13 +304,12 @@ TEST(ThreadSafeMemoizerTest, CopyAssignment_CopyToSelf_MemoizedValue) { ThreadSafeMemoizer memoizer; auto& looks_like_another_memoizer = memoizer; ASSERT_EQ(&memoizer, &looks_like_another_memoizer); - const auto memoized_value = memoizer.value(func); - const auto expected_invocation_count = memoizer_counter.invocation_count(); + memoizer.value(func); memoizer = looks_like_another_memoizer; - EXPECT_EQ(memoizer.value(func), memoized_value); - EXPECT_EQ(memoizer_counter.invocation_count(), expected_invocation_count); + EXPECT_EQ(memoizer.value(func), "aaa_0"); + EXPECT_EQ(memoizer_counter.invocation_count(), 1); } TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToNoMemoizedValue) { @@ -351,21 +344,15 @@ TEST(ThreadSafeMemoizerTest, MoveAssignment_MemoizedValueToMemoizedValue) { memoizer_move_dest_counter1("bbb1"), memoizer_move_dest_counter2("bbb2"); ThreadSafeMemoizer memoizer; memoizer.value(memoizer_counter1.func()); - const auto expected_memoizer_counter1_invocation_count = - memoizer_counter1.invocation_count(); ThreadSafeMemoizer memoizer_move_dest; memoizer_move_dest.value(memoizer_move_dest_counter1.func()); - const auto expected_memoizer_move_dest_counter1_invocation_count = - memoizer_move_dest_counter1.invocation_count(); memoizer_move_dest = std::move(memoizer); EXPECT_EQ(memoizer_move_dest.value(memoizer_move_dest_counter2.func()), "aaa1"); - EXPECT_EQ(memoizer_counter1.invocation_count(), - expected_memoizer_counter1_invocation_count); - EXPECT_EQ(memoizer_move_dest_counter1.invocation_count(), - expected_memoizer_move_dest_counter1_invocation_count); + EXPECT_EQ(memoizer_counter1.invocation_count(), 1); + EXPECT_EQ(memoizer_move_dest_counter1.invocation_count(), 1); VerifyWorksAfterBeingAssigned(memoizer); } @@ -465,8 +452,13 @@ TEST(ThreadSafeMemoizerTest, TSAN_ConcurrentCallsToValueShouldNotDataRace) { CountDownLatch latch(num_threads); std::vector threads; for (auto i = num_threads; i > 0; --i) { - threads.emplace_back([i, &latch, &memoizer] { + threads.emplace_back([i, num_threads, &latch, &memoizer] { latch.arrive_and_wait(); + + for (auto k = num_threads; k > 0; --k) { + std::this_thread::yield(); + } + memoizer.value([i] { return std::make_shared(i); }); }); } @@ -509,31 +501,32 @@ TEST(ThreadSafeMemoizerTest, TSAN_ValueInACopyShouldNotDataRace) { } void VerifyWorksAfterBeingAssigned(ThreadSafeMemoizer& memoizer) { - ThreadSafeMemoizer memoizer2; - CountingFunc counter2("sx22pz64dn_%s"); - auto func2 = counter2.func(); - const bool counter2_had_memoized_value = GenerateRandomBool(); + ThreadSafeMemoizer other_memoizer; + CountingFunc other_memoizer_counter("sx22pz64dn_%s"); // Randomly select whether the original memoizer had a memoized value. - const std::string memoized_value = counter2_had_memoized_value - ? memoizer2.value(func2) - : "(error code nnwyh34mtx)"; - const auto invocation_count_before = counter2.invocation_count(); + const bool other_memoizer_has_memoized_value = GenerateRandomBool(); + if (other_memoizer_has_memoized_value) { + other_memoizer.value(other_memoizer_counter.func()); + } // Randomly select copy-assignment or move-assignment. if (GenerateRandomBool()) { - memoizer = memoizer2; + memoizer = other_memoizer; } else { - memoizer = std::move(memoizer2); + memoizer = std::move(other_memoizer); } - if (counter2_had_memoized_value) { - EXPECT_EQ(memoizer.value(func2), memoized_value); - EXPECT_EQ(counter2.invocation_count(), invocation_count_before); + // Verify that the given `ThreadSafeMemoizer` behaves correctly after being + // assigned. + if (other_memoizer_has_memoized_value) { + EXPECT_EQ(memoizer.value(other_memoizer_counter.func()), "sx22pz64dn_0"); + EXPECT_EQ(other_memoizer_counter.invocation_count(), 1); } else { - CountingFunc counter3("mx3rfb8qqk"); - EXPECT_EQ(memoizer.value(counter3.func()), "mx3rfb8qqk"); - EXPECT_EQ(counter2.invocation_count(), invocation_count_before); + CountingFunc temp_counter("mx3rfb8qqk"); + EXPECT_EQ(memoizer.value(temp_counter.func()), "mx3rfb8qqk"); + EXPECT_EQ(temp_counter.invocation_count(), 1); + EXPECT_EQ(other_memoizer_counter.invocation_count(), 0); } } diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index 5e127bb7ec6..b30f0d4cab0 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -97,13 +97,13 @@ std::string CountingFunc::Next(const std::string& cookie) { CountDownLatch::CountDownLatch(int count) { // Explicitly store the count into the atomic because initialization is // NOT atomic. - count_.store(count); + count_.store(count, std::memory_order_release); } void CountDownLatch::arrive_and_wait() { - count_.fetch_sub(1); - while (count_.load() > 0) { - std::this_thread::yield(); + count_.fetch_sub(1, std::memory_order_acq_rel); + while (count_.load(std::memory_order_acquire) > 0) { + // do nothing; busy wait. } } From 8f5d1e91f239a1ff6cc1c5578cc7176c0e60955e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 14 Jan 2025 17:57:34 +0000 Subject: [PATCH 84/84] fix when running under tsan --- .../unit/util/thread_safe_memoizer_test.cc | 2 +- .../unit/util/thread_safe_memoizer_testing.cc | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc index e48faf654b0..de984c181f0 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -448,7 +448,7 @@ TEST(ThreadSafeMemoizerTest, TEST(ThreadSafeMemoizerTest, TSAN_ConcurrentCallsToValueShouldNotDataRace) { ThreadSafeMemoizer memoizer; - const auto num_threads = max_practical_parallel_threads_for_testing() * 4; + const auto num_threads = max_practical_parallel_threads_for_testing(); CountDownLatch latch(num_threads); std::vector threads; for (auto i = num_threads; i > 0; --i) { diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc index b30f0d4cab0..998bf41bd6f 100644 --- a/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_testing.cc @@ -16,6 +16,7 @@ #include "Firestore/core/test/unit/util/thread_safe_memoizer_testing.h" +#include #include #include #include @@ -26,11 +27,20 @@ #include #include +#include "Firestore/core/src/util/sanitizers.h" + namespace firebase { namespace firestore { namespace testing { namespace { +constexpr bool kIsRunningUnderThreadSanitizer = +#if THREAD_SANITIZER + true; +#else + false; +#endif + std::vector SplitSeparators(const std::string& s) { std::vector chunks; @@ -103,14 +113,23 @@ CountDownLatch::CountDownLatch(int count) { void CountDownLatch::arrive_and_wait() { count_.fetch_sub(1, std::memory_order_acq_rel); while (count_.load(std::memory_order_acquire) > 0) { - // do nothing; busy wait. + std::this_thread::yield(); } } decltype(std::thread::hardware_concurrency()) max_practical_parallel_threads_for_testing() { const auto hardware_concurrency = std::thread::hardware_concurrency(); - return hardware_concurrency != 0 ? hardware_concurrency : 4; + const auto num_threads = hardware_concurrency != 0 ? hardware_concurrency : 4; + + // Limit the number of threads when running under Thread Sanitizer as the + // boilerplate that it puts around atomics is so much that a large number of + // threads competing for a std::atomic can bring the app to its knees. + if (kIsRunningUnderThreadSanitizer) { + return std::min(static_cast(num_threads), 10); + } + + return num_threads; } bool GenerateRandomBool() {