Skip to content

Commit c5bf1e5

Browse files
authored
Firestore: Fix memory leak in Query.whereField() (#14300)
1 parent 998dbec commit c5bf1e5

15 files changed

+1310
-146
lines changed

Firestore/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [fixed] Fixed memory leak in `Query.whereField()`. (#13978)
3+
14
# 11.8.0
25
- [fixed] Fixed use-after-free bug when internally formatting strings. (#14306)
36
- [changed] Update gRPC dependency to 1.69.

Firestore/Example/Firestore.xcodeproj/project.pbxproj

+30
Large diffs are not rendered by default.

Firestore/core/src/core/composite_filter.cc

+9-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/core/composite_filter.h"
1818

1919
#include <algorithm>
20+
#include <memory>
2021
#include <utility>
2122

2223
#include "Firestore/core/src/core/field_filter.h"
@@ -141,16 +142,14 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter(
141142
return nullptr;
142143
}
143144

144-
const std::vector<FieldFilter>& CompositeFilter::Rep::GetFlattenedFilters()
145-
const {
146-
return memoized_flattened_filters_->memoize([&]() {
147-
std::vector<FieldFilter> flattened_filters;
148-
for (const auto& filter : filters())
149-
std::copy(filter.GetFlattenedFilters().begin(),
150-
filter.GetFlattenedFilters().end(),
151-
std::back_inserter(flattened_filters));
152-
return flattened_filters;
153-
});
145+
std::shared_ptr<std::vector<FieldFilter>>
146+
CompositeFilter::Rep::CalculateFlattenedFilters() const {
147+
auto flattened_filters = std::make_shared<std::vector<FieldFilter>>();
148+
for (const auto& filter : filters())
149+
std::copy(filter.GetFlattenedFilters().begin(),
150+
filter.GetFlattenedFilters().end(),
151+
std::back_inserter(*flattened_filters));
152+
return flattened_filters;
154153
}
155154

156155
} // namespace core

Firestore/core/src/core/composite_filter.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ class CompositeFilter : public Filter {
138138
return filters_.empty();
139139
}
140140

141-
const std::vector<FieldFilter>& GetFlattenedFilters() const override;
141+
std::shared_ptr<std::vector<FieldFilter>> CalculateFlattenedFilters()
142+
const override;
142143

143144
std::vector<Filter> GetFilters() const override {
144145
return filters();

Firestore/core/src/core/field_filter.cc

+6-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "Firestore/core/src/core/field_filter.h"
1818

19+
#include <memory>
1920
#include <utility>
2021

2122
#include "Firestore/core/src/core/array_contains_any_filter.h"
@@ -122,12 +123,12 @@ FieldFilter::FieldFilter(std::shared_ptr<const Filter::Rep> rep)
122123
: Filter(std::move(rep)) {
123124
}
124125

125-
const std::vector<FieldFilter>& FieldFilter::Rep::GetFlattenedFilters() const {
126+
std::shared_ptr<std::vector<FieldFilter>>
127+
FieldFilter::Rep::CalculateFlattenedFilters() const {
126128
// This is already a field filter, so we return a vector of size one.
127-
return memoized_flattened_filters_->memoize([&]() {
128-
return std::vector<FieldFilter>{
129-
FieldFilter(std::make_shared<const Rep>(*this))};
130-
});
129+
auto filters = std::make_shared<std::vector<FieldFilter>>();
130+
filters->push_back(FieldFilter(std::make_shared<const Rep>(*this)));
131+
return filters;
131132
}
132133

133134
std::vector<Filter> FieldFilter::Rep::GetFilters() const {

Firestore/core/src/core/field_filter.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ class FieldFilter : public Filter {
117117
return false;
118118
}
119119

120-
const std::vector<FieldFilter>& GetFlattenedFilters() const override;
121-
122120
std::vector<Filter> GetFilters() const override;
123121

124122
protected:
@@ -140,6 +138,9 @@ class FieldFilter : public Filter {
140138

141139
bool MatchesComparison(util::ComparisonResult comparison) const;
142140

141+
std::shared_ptr<std::vector<FieldFilter>> CalculateFlattenedFilters()
142+
const override;
143+
143144
private:
144145
friend class FieldFilter;
145146

Firestore/core/src/core/filter.cc

-6
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@ std::ostream& operator<<(std::ostream& os, const Filter& filter) {
3535
return os << filter.ToString();
3636
}
3737

38-
Filter::Rep::Rep()
39-
: memoized_flattened_filters_(
40-
std::make_shared<
41-
util::ThreadSafeMemoizer<std::vector<FieldFilter>>>()) {
42-
}
43-
4438
} // namespace core
4539
} // namespace firestore
4640
} // namespace firebase

Firestore/core/src/core/filter.h

+12-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef FIRESTORE_CORE_SRC_CORE_FILTER_H_
1818
#define FIRESTORE_CORE_SRC_CORE_FILTER_H_
1919

20+
#include <functional>
2021
#include <iosfwd>
2122
#include <memory>
2223
#include <string>
@@ -114,7 +115,7 @@ class Filter {
114115
protected:
115116
class Rep {
116117
public:
117-
Rep();
118+
Rep() = default;
118119

119120
virtual ~Rep() = default;
120121

@@ -147,20 +148,23 @@ class Filter {
147148

148149
virtual bool IsEmpty() const = 0;
149150

150-
virtual const std::vector<FieldFilter>& GetFlattenedFilters() const = 0;
151+
virtual const std::vector<FieldFilter>& GetFlattenedFilters() const {
152+
const auto func = std::bind(&Rep::CalculateFlattenedFilters, this);
153+
return memoized_flattened_filters_.value(func);
154+
}
151155

152156
virtual std::vector<Filter> GetFilters() const = 0;
153157

158+
protected:
159+
virtual std::shared_ptr<std::vector<FieldFilter>>
160+
CalculateFlattenedFilters() const = 0;
161+
162+
private:
154163
/**
155164
* Memoized list of all field filters that can be found by
156165
* traversing the tree of filters contained in this composite filter.
157-
*
158-
* Use a `std::shared_ptr<ThreadSafeMemoizer>` rather than using
159-
* `ThreadSafeMemoizer` directly so that this class is copyable
160-
* (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag`
161-
* member variable, which is not copyable).
162166
*/
163-
mutable std::shared_ptr<util::ThreadSafeMemoizer<std::vector<FieldFilter>>>
167+
mutable util::ThreadSafeMemoizer<std::vector<FieldFilter>>
164168
memoized_flattened_filters_;
165169
};
166170

Firestore/core/src/core/query.cc

+37-40
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/core/query.h"
1818

1919
#include <algorithm>
20+
#include <memory>
2021
#include <ostream>
2122

2223
#include "Firestore/core/src/core/bound.h"
@@ -91,44 +92,42 @@ absl::optional<Operator> Query::FindOpInsideFilters(
9192
return absl::nullopt;
9293
}
9394

94-
const std::vector<OrderBy>& Query::normalized_order_bys() const {
95-
return memoized_normalized_order_bys_->memoize([&]() {
96-
// Any explicit order by fields should be added as is.
97-
std::vector<OrderBy> result = explicit_order_bys_;
98-
std::set<FieldPath> fieldsNormalized;
99-
for (const OrderBy& order_by : explicit_order_bys_) {
100-
fieldsNormalized.insert(order_by.field());
101-
}
95+
std::shared_ptr<std::vector<OrderBy>> Query::CalculateNormalizedOrderBys()
96+
const {
97+
// Any explicit order by fields should be added as is.
98+
auto result = std::make_shared<std::vector<OrderBy>>(explicit_order_bys_);
99+
std::set<FieldPath> fieldsNormalized;
100+
for (const OrderBy& order_by : explicit_order_bys_) {
101+
fieldsNormalized.insert(order_by.field());
102+
}
102103

103-
// The order of the implicit ordering always matches the last explicit order
104-
// by.
105-
Direction last_direction = explicit_order_bys_.empty()
106-
? Direction::Ascending
107-
: explicit_order_bys_.back().direction();
108-
109-
// Any inequality fields not explicitly ordered should be implicitly ordered
110-
// in a lexicographical order. When there are multiple inequality filters on
111-
// the same field, the field should be added only once. Note:
112-
// `std::set<model::FieldPath>` sorts the key field before other fields.
113-
// However, we want the key field to be sorted last.
114-
const std::set<model::FieldPath> inequality_fields =
115-
InequalityFilterFields();
116-
117-
for (const model::FieldPath& field : inequality_fields) {
118-
if (fieldsNormalized.find(field) == fieldsNormalized.end() &&
119-
!field.IsKeyFieldPath()) {
120-
result.push_back(OrderBy(field, last_direction));
121-
}
104+
// The order of the implicit ordering always matches the last explicit order
105+
// by.
106+
Direction last_direction = explicit_order_bys_.empty()
107+
? Direction::Ascending
108+
: explicit_order_bys_.back().direction();
109+
110+
// Any inequality fields not explicitly ordered should be implicitly ordered
111+
// in a lexicographical order. When there are multiple inequality filters on
112+
// the same field, the field should be added only once. Note:
113+
// `std::set<model::FieldPath>` sorts the key field before other fields.
114+
// However, we want the key field to be sorted last.
115+
const std::set<model::FieldPath> inequality_fields = InequalityFilterFields();
116+
117+
for (const model::FieldPath& field : inequality_fields) {
118+
if (fieldsNormalized.find(field) == fieldsNormalized.end() &&
119+
!field.IsKeyFieldPath()) {
120+
result->push_back(OrderBy(field, last_direction));
122121
}
122+
}
123123

124-
// Add the document key field to the last if it is not explicitly ordered.
125-
if (fieldsNormalized.find(FieldPath::KeyFieldPath()) ==
126-
fieldsNormalized.end()) {
127-
result.push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction));
128-
}
124+
// Add the document key field to the last if it is not explicitly ordered.
125+
if (fieldsNormalized.find(FieldPath::KeyFieldPath()) ==
126+
fieldsNormalized.end()) {
127+
result->push_back(OrderBy(FieldPath::KeyFieldPath(), last_direction));
128+
}
129129

130-
return result;
131-
});
130+
return result;
132131
}
133132

134133
LimitType Query::limit_type() const {
@@ -296,14 +295,12 @@ std::string Query::ToString() const {
296295
return absl::StrCat("Query(canonical_id=", CanonicalId(), ")");
297296
}
298297

299-
const Target& Query::ToTarget() const& {
300-
return memoized_target_->memoize(
301-
[&]() { return ToTarget(normalized_order_bys()); });
298+
std::shared_ptr<Target> Query::CalculateTarget() const {
299+
return std::make_shared<Target>(ToTarget(normalized_order_bys()));
302300
}
303301

304-
const Target& Query::ToAggregateTarget() const& {
305-
return memoized_aggregate_target_->memoize(
306-
[&]() { return ToTarget(explicit_order_bys_); });
302+
std::shared_ptr<Target> Query::CalculateAggregateTarget() const {
303+
return std::make_shared<Target>(ToTarget(explicit_order_bys_));
307304
}
308305

309306
Target Query::ToTarget(const std::vector<OrderBy>& order_bys) const {

Firestore/core/src/core/query.h

+20-11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef FIRESTORE_CORE_SRC_CORE_QUERY_H_
1818
#define FIRESTORE_CORE_SRC_CORE_QUERY_H_
1919

20+
#include <functional>
2021
#include <iosfwd>
2122
#include <limits>
2223
#include <memory>
@@ -148,7 +149,10 @@ class Query {
148149
* This might include additional sort orders added implicitly to match the
149150
* backend behavior.
150151
*/
151-
const std::vector<OrderBy>& normalized_order_bys() const;
152+
const std::vector<OrderBy>& normalized_order_bys() const {
153+
const auto func = std::bind(&Query::CalculateNormalizedOrderBys, this);
154+
return memoized_normalized_order_bys_.value(func);
155+
}
152156

153157
bool has_limit() const {
154158
return limit_ != Target::kNoLimit;
@@ -246,15 +250,21 @@ class Query {
246250
* Returns a `Target` instance this query will be mapped to in backend
247251
* and local store.
248252
*/
249-
const Target& ToTarget() const&;
253+
const Target& ToTarget() const& {
254+
const auto func = std::bind(&Query::CalculateTarget, this);
255+
return memoized_target_.value(func);
256+
}
250257

251258
/**
252259
* Returns a `Target` instance this query will be mapped to in backend
253260
* and local store, for use within an aggregate query. Unlike targets
254261
* for non-aggregate queries, aggregate query targets do not contain
255262
* normalized order-bys, they only contain explicit order-bys.
256263
*/
257-
const Target& ToAggregateTarget() const&;
264+
const Target& ToAggregateTarget() const& {
265+
const auto func = std::bind(&Query::CalculateAggregateTarget, this);
266+
return memoized_aggregate_target_.value(func);
267+
}
258268

259269
friend std::ostream& operator<<(std::ostream& os, const Query& query);
260270

@@ -295,20 +305,19 @@ class Query {
295305
// member variable, which is not copyable).
296306

297307
// The memoized list of sort orders.
298-
mutable std::shared_ptr<util::ThreadSafeMemoizer<std::vector<OrderBy>>>
299-
memoized_normalized_order_bys_{
300-
std::make_shared<util::ThreadSafeMemoizer<std::vector<OrderBy>>>()};
308+
std::shared_ptr<std::vector<OrderBy>> CalculateNormalizedOrderBys() const;
309+
mutable util::ThreadSafeMemoizer<std::vector<OrderBy>>
310+
memoized_normalized_order_bys_;
301311

302312
// The corresponding Target of this Query instance.
303-
mutable std::shared_ptr<util::ThreadSafeMemoizer<Target>> memoized_target_{
304-
std::make_shared<util::ThreadSafeMemoizer<Target>>()};
313+
std::shared_ptr<Target> CalculateTarget() const;
314+
mutable util::ThreadSafeMemoizer<Target> memoized_target_;
305315

306316
// The corresponding aggregate Target of this Query instance. Unlike targets
307317
// for non-aggregate queries, aggregate query targets do not contain
308318
// normalized order-bys, they only contain explicit order-bys.
309-
mutable std::shared_ptr<util::ThreadSafeMemoizer<Target>>
310-
memoized_aggregate_target_{
311-
std::make_shared<util::ThreadSafeMemoizer<Target>>()};
319+
std::shared_ptr<Target> CalculateAggregateTarget() const;
320+
mutable util::ThreadSafeMemoizer<Target> memoized_aggregate_target_;
312321
};
313322

314323
bool operator==(const Query& lhs, const Query& rhs);

0 commit comments

Comments
 (0)