Skip to content

Commit 9a4f4e7

Browse files
authored
Miscellaneous fixes (#4584)
* Fix use after move in LevelDbPersistenceForTesting * Fix size-related errors when compiling in 32-bit mode * Fix test-only memory leaks * Fix test-only memory leaks * Prevent spec tests from appearing to leak The spec tests aren't actually leaking, but they accumulate enough garbage without clearing the autorelease pool that Instruments complains. * Avoid temporaries in methods that @throw ARC does not release temporaries when an Objective-C exception is thrown. Use C++ equivalents directly to avoid leaks as detected by Instruments. * Fix memory leak in acknowledge mutation Any previous value must be freed before the next one is assigned. * Manually implement assignment operators in ByteString The single by-value implementation wasn't preventing the compiler generated standard versions from being generated which was causing leaks. * Prevent leaking GrpcCompletions that complete after Shutdown After shutdown, the AsyncQueue silently drops Enqueue requests which prevents these blocks from being deleted. * Avoid racing with enqueued operations It's possible for the Executor to immediately execute the given block, at which point the block might observe the async queue in a not-shutting-down state.
1 parent 3dc1185 commit 9a4f4e7

File tree

20 files changed

+202
-97
lines changed

20 files changed

+202
-97
lines changed

Firestore/Example/Tests/Integration/FSTTransactionTests.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,8 @@ - (void)runSuccessfulTransaction {
233233
completion:^(id _Nullable result, NSError *_Nullable error) {
234234
[expectation fulfill];
235235
NSString *message =
236-
[NSString stringWithFormat:@"Expected the sequence %@, to succeed, but got %ld.",
237-
[self stageNames], [error code]];
236+
[NSString stringWithFormat:@"Expected the sequence %@, to succeed, but got %d.",
237+
[self stageNames], (int)[error code]];
238238
[self->_testCase assertNilError:error message:message];
239239
}];
240240

Firestore/Example/Tests/SpecTests/FSTSpecTests.mm

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ - (void)setUpForSpecWithConfig:(NSDictionary *)config {
189189

190190
- (void)tearDownForSpec {
191191
[self.driver shutdown];
192+
193+
// Help ARC realize that everything here can be collected earlier.
194+
_driver = nil;
192195
}
193196

194197
/**
@@ -749,24 +752,26 @@ - (void)validateActiveTargets {
749752
}
750753

751754
- (void)runSpecTestSteps:(NSArray *)steps config:(NSDictionary *)config {
752-
@try {
753-
[self setUpForSpecWithConfig:config];
754-
for (NSDictionary *step in steps) {
755-
LOG_DEBUG("Doing step %s", step);
756-
[self doStep:step];
757-
[self validateExpectedSnapshotEvents:step[@"expectedSnapshotEvents"]];
758-
[self validateExpectedState:step[@"expectedState"]];
759-
int expectedSnapshotsInSyncEvents = [step[@"expectedSnapshotsInSyncEvents"] intValue];
760-
[self validateSnapshotsInSyncEvents:expectedSnapshotsInSyncEvents];
755+
@autoreleasepool {
756+
@try {
757+
[self setUpForSpecWithConfig:config];
758+
for (NSDictionary *step in steps) {
759+
LOG_DEBUG("Doing step %s", step);
760+
[self doStep:step];
761+
[self validateExpectedSnapshotEvents:step[@"expectedSnapshotEvents"]];
762+
[self validateExpectedState:step[@"expectedState"]];
763+
int expectedSnapshotsInSyncEvents = [step[@"expectedSnapshotsInSyncEvents"] intValue];
764+
[self validateSnapshotsInSyncEvents:expectedSnapshotsInSyncEvents];
765+
}
766+
[self.driver validateUsage];
767+
} @finally {
768+
// Ensure that the driver is torn down even if the test is failing due to a thrown exception
769+
// so that any resources held by the driver are released. This is important when the driver is
770+
// backed by LevelDB because LevelDB locks its database. If -tearDownForSpec were not called
771+
// after an exception then subsequent attempts to open the LevelDB will fail, making it harder
772+
// to zero in on the spec tests as a culprit.
773+
[self tearDownForSpec];
761774
}
762-
[self.driver validateUsage];
763-
} @finally {
764-
// Ensure that the driver is torn down even if the test is failing due to a thrown exception so
765-
// that any resources held by the driver are released. This is important when the driver is
766-
// backed by LevelDB because LevelDB locks its database. If -tearDownForSpec were not called
767-
// after an exception then subsequent attempts to open the LevelDB will fail, making it harder
768-
// to zero in on the spec tests as a culprit.
769-
[self tearDownForSpec];
770775
}
771776
}
772777

Firestore/Source/API/FIRDocumentSnapshot.mm

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@
5353
using firebase::firestore::model::DatabaseId;
5454
using firebase::firestore::model::Document;
5555
using firebase::firestore::model::DocumentKey;
56+
using firebase::firestore::model::FieldPath;
5657
using firebase::firestore::model::FieldValue;
5758
using firebase::firestore::model::FieldValueOptions;
5859
using firebase::firestore::model::ObjectValue;
5960
using firebase::firestore::model::ServerTimestampBehavior;
6061
using firebase::firestore::nanopb::MakeNSData;
62+
using firebase::firestore::util::MakeString;
6163
using firebase::firestore::util::ThrowInvalidArgument;
6264

6365
NS_ASSUME_NONNULL_BEGIN
@@ -180,16 +182,16 @@ - (nullable id)valueForField:(id)field {
180182

181183
- (nullable id)valueForField:(id)field
182184
serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior {
183-
FIRFieldPath *fieldPath;
185+
FieldPath fieldPath;
184186
if ([field isKindOfClass:[NSString class]]) {
185-
fieldPath = [FIRFieldPath pathWithDotSeparatedString:field];
187+
fieldPath = FieldPath::FromDotSeparatedString(MakeString(field));
186188
} else if ([field isKindOfClass:[FIRFieldPath class]]) {
187-
fieldPath = field;
189+
fieldPath = ((FIRFieldPath *)field).internalValue;
188190
} else {
189191
ThrowInvalidArgument("Subscript key must be an NSString or FIRFieldPath.");
190192
}
191193

192-
absl::optional<FieldValue> fieldValue = _snapshot.GetValue(fieldPath.internalValue);
194+
absl::optional<FieldValue> fieldValue = _snapshot.GetValue(fieldPath);
193195
FieldValueOptions options = [self optionsForServerTimestampBehavior:serverTimestampBehavior];
194196
return !fieldValue ? nil : [self convertedValue:*fieldValue options:options];
195197
}

Firestore/Source/API/FIRQuery.mm

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
using firebase::firestore::model::FieldValue;
7979
using firebase::firestore::model::ResourcePath;
8080
using firebase::firestore::util::MakeNSError;
81+
using firebase::firestore::util::MakeString;
8182
using firebase::firestore::util::StatusOr;
8283
using firebase::firestore::util::ThrowInvalidArgument;
8384

@@ -86,7 +87,7 @@
8687
namespace {
8788

8889
FieldPath MakeFieldPath(NSString *field) {
89-
return FieldPath::FromDotSeparatedString(util::MakeString(field));
90+
return FieldPath::FromDotSeparatedString(MakeString(field));
9091
}
9192

9293
FIRQuery *Wrap(Query &&query) {
@@ -356,21 +357,25 @@ - (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate {
356357
}
357358

358359
- (FIRQuery *)queryOrderedByField:(NSString *)field {
359-
return [self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]
360-
descending:NO];
360+
return [self queryOrderedByField:field descending:NO];
361361
}
362362

363363
- (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath {
364364
return [self queryOrderedByFieldPath:fieldPath descending:NO];
365365
}
366366

367367
- (FIRQuery *)queryOrderedByField:(NSString *)field descending:(BOOL)descending {
368-
return [self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]
369-
descending:descending];
368+
return [self queryOrderedByFieldPath:MakeFieldPath(field)
369+
direction:Direction::FromDescending(descending)];
370370
}
371371

372372
- (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath descending:(BOOL)descending {
373-
return Wrap(_query.OrderBy(fieldPath.internalValue, Direction::FromDescending(descending)));
373+
return [self queryOrderedByFieldPath:fieldPath.internalValue
374+
direction:Direction::FromDescending(descending)];
375+
}
376+
377+
- (FIRQuery *)queryOrderedByFieldPath:(model::FieldPath)fieldPath direction:(Direction)direction {
378+
return Wrap(_query.OrderBy(std::move(fieldPath), direction));
374379
}
375380

376381
- (FIRQuery *)queryLimitedTo:(NSInteger)limit {
@@ -468,7 +473,7 @@ - (FIRQuery *)queryWithFilterOperator:(Filter::Operator)filterOperator
468473
value:(id)value {
469474
FieldValue fieldValue = [self parsedQueryValue:value
470475
allowArrays:filterOperator == Filter::Operator::In];
471-
auto describer = [value] { return util::MakeString(NSStringFromClass([value class])); };
476+
auto describer = [value] { return MakeString(NSStringFromClass([value class])); };
472477
return Wrap(_query.Filter(fieldPath, filterOperator, std::move(fieldValue), describer));
473478
}
474479

Firestore/Source/API/FSTUserDataConverter.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ - (ParsedUpdateData)parsedUpdateData:(id)input {
200200
FieldPath path;
201201

202202
if ([key isKindOfClass:[NSString class]]) {
203-
path = [FIRFieldPath pathWithDotSeparatedString:key].internalValue;
203+
path = FieldPath::FromDotSeparatedString(util::MakeString(key));
204204
} else if ([key isKindOfClass:[FIRFieldPath class]]) {
205205
path = ((FIRFieldPath *)key).internalValue;
206206
} else {

Firestore/Swift/Tests/Codable/FirestoreEncoderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ class FirestoreEncoderTests: XCTestCase {
229229
"s": "abc",
230230
"d": 123,
231231
"f": -4,
232-
"l": 1_234_567_890_123,
232+
"l": Int64(1_234_567_890_123),
233233
"i": -4444,
234234
"b": false,
235235
"sh": 123,

Firestore/core/src/firebase/firestore/local/leveldb_mutation_queue.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,8 @@ ByteString LevelDbMutationQueue::GetLastStreamToken() {
421421
}
422422

423423
void LevelDbMutationQueue::SetLastStreamToken(ByteString stream_token) {
424+
std::free(metadata_->last_stream_token);
425+
424426
metadata_->last_stream_token = stream_token.release();
425427
db_->current_transaction()->Put(mutation_queue_key(), metadata_);
426428
}

Firestore/core/src/firebase/firestore/nanopb/byte_string.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,31 @@ ByteString::ByteString(const ByteString& other)
5555
}
5656

5757
ByteString::ByteString(ByteString&& other) noexcept {
58-
swap(*this, other);
58+
bytes_ = other.bytes_;
59+
other.bytes_ = nullptr;
5960
}
6061

6162
ByteString::~ByteString() {
6263
std::free(bytes_);
6364
}
6465

66+
ByteString& ByteString::operator=(const ByteString& other) {
67+
if (bytes_ != other.bytes_) {
68+
std::free(bytes_);
69+
bytes_ = MakeBytesArray(other.data(), other.size());
70+
}
71+
return *this;
72+
}
73+
74+
ByteString& ByteString::operator=(ByteString&& other) noexcept {
75+
if (bytes_ != other.bytes_) {
76+
std::free(bytes_);
77+
bytes_ = other.bytes_;
78+
other.bytes_ = nullptr;
79+
}
80+
return *this;
81+
}
82+
6583
/* static */ ByteString ByteString::Take(pb_bytes_array_t* bytes) {
6684
return ByteString{bytes, 0};
6785
}

Firestore/core/src/firebase/firestore/nanopb/byte_string.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,8 @@ class ByteString : public util::Comparable<ByteString> {
8080

8181
~ByteString();
8282

83-
ByteString& operator=(ByteString other) {
84-
swap(*this, other);
85-
return *this;
86-
}
83+
ByteString& operator=(const ByteString& other);
84+
ByteString& operator=(ByteString&& other) noexcept;
8785

8886
friend void swap(ByteString& lhs, ByteString& rhs) noexcept;
8987

Firestore/core/src/firebase/firestore/remote/grpc_completion.cc

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ namespace remote {
2525

2626
using util::AsyncQueue;
2727

28+
std::shared_ptr<GrpcCompletion> GrpcCompletion::Create(
29+
Type type,
30+
const std::shared_ptr<util::AsyncQueue>& worker_queue,
31+
Callback&& callback) {
32+
// Construct in two steps to use the private constructor.
33+
GrpcCompletion partial(type, worker_queue, std::move(callback));
34+
auto completion = std::make_shared<GrpcCompletion>(std::move(partial));
35+
36+
// Prepare the `GrpcCompletion` for submission to gRPC.
37+
//
38+
// Note: this is done in a separate step because `shared_from_this` cannot be
39+
// called in a constructor.
40+
completion->grpc_ownership_ = completion;
41+
42+
return completion;
43+
}
44+
2845
GrpcCompletion::GrpcCompletion(
2946
Type type,
3047
const std::shared_ptr<util::AsyncQueue>& worker_queue,
@@ -35,13 +52,17 @@ GrpcCompletion::GrpcCompletion(
3552
void GrpcCompletion::Cancel() {
3653
worker_queue_->VerifyIsCurrentQueue();
3754
callback_ = {};
55+
56+
// Does not release grpc_ownership_. If gRPC still holds this completion it
57+
// must remain valid to avoid a use-after-free once Complete is actually
58+
// called.
3859
}
3960

4061
void GrpcCompletion::WaitUntilOffQueue() {
4162
worker_queue_->VerifyIsCurrentQueue();
4263

4364
EnsureValidFuture();
44-
return off_queue_future_.wait();
65+
off_queue_future_.wait();
4566
}
4667

4768
std::future_status GrpcCompletion::WaitUntilOffQueue(
@@ -64,12 +85,21 @@ void GrpcCompletion::Complete(bool ok) {
6485
// objects to be valid).
6586
off_queue_.set_value();
6687

67-
worker_queue_->Enqueue([this, ok] {
68-
if (callback_) {
69-
callback_(ok, this);
88+
// The queued operation needs to also retain this completion. It's possible
89+
// for Complete to fire, shutdown to start, and then have this queued
90+
// operation run. If this weren't a retain that ordering would have the
91+
// callback use after free.
92+
auto shared_this = grpc_ownership_;
93+
worker_queue_->Enqueue([shared_this, ok] {
94+
if (shared_this->callback_) {
95+
shared_this->callback_(ok, shared_this);
7096
}
71-
delete this;
7297
});
98+
99+
// Having called Complete, gRPC has released its ownership interest in this
100+
// object. Once the queued operation completes the `GrpcCompletion` will be
101+
// deleted.
102+
grpc_ownership_.reset();
73103
}
74104

75105
} // namespace remote

0 commit comments

Comments
 (0)