Skip to content

Commit fde87af

Browse files
smilesa-maurice
authored andcommitted
Fixed race on cleanup of database query value and child listeners on iOS.
The assumption that FIRDatabaseQuery:removeObserverWithHandle:handle is synchronous - it's not - could lead to C++ objects being referenced from observer blocks after C++ Database, Query, ChildListener and ValueListener objects have been destroyed. This changes all Objective-C observer blocks to reference the Objective-C FIRCPPDatabaseQueryCallbackState object instead of using direct references to C++ objects. Using FIRCPPDatabaseQueryCallbackState it's possible to take advantage of ARC to handle the cleanup of the observer state and also to remove references to C++ objects from each observer before C++ objects are destroyed. This also, fixes all compilation warnings due to missing pointer requirement annotations. PiperOrigin-RevId: 250547302
1 parent f7e67fc commit fde87af

File tree

8 files changed

+362
-171
lines changed

8 files changed

+362
-171
lines changed

database/src/ios/data_snapshot_ios.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class DatabaseReferenceInternal;
4040
// wrapper around the FIRDataSnapshot Obj-C class.
4141
OBJ_C_PTR_WRAPPER(FIRDataSnapshot);
4242

43+
#pragma clang assume_nonnull begin
44+
4345
// The iOS implementation of the DataSnapshot, which contains data from a
4446
// Firebase Database location.
4547
class DataSnapshotInternal {
@@ -109,7 +111,7 @@ class DataSnapshotInternal {
109111

110112
private:
111113
#ifdef __OBJC__
112-
FIRDataSnapshot* _Nonnull impl() const { return impl_->ptr; }
114+
FIRDataSnapshot* impl() const { return impl_->ptr; }
113115
#endif // __OBJC__
114116

115117
DatabaseInternal* database_;
@@ -118,6 +120,8 @@ class DataSnapshotInternal {
118120
UniquePtr<FIRDataSnapshotPointer> impl_;
119121
};
120122

123+
#pragma clang assume_nonnull end
124+
121125
} // namespace internal
122126
} // namespace database
123127
} // namespace firebase

database/src/ios/database_ios.h

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ namespace internal {
4040
// This defines the class FIRDatabasePointer, which is a C++-compatible wrapper
4141
// around the FIRDatabase Obj-C class.
4242
OBJ_C_PTR_WRAPPER(FIRDatabase);
43+
// This defines the class firebase::database::internal::NSRecursiveLockPointer,
44+
// which is a C++-compatible wrapper around the NSRecursiveLock Obj-C class,
45+
// used by observer callbacks of FIRDatabaseQuery.
46+
OBJ_C_PTR_WRAPPER(NSRecursiveLock);
47+
48+
#pragma clang assume_nonnull begin
4349

4450
// This is the iOS implementation of Database.
4551
class DatabaseInternal {
@@ -82,9 +88,9 @@ class DatabaseInternal {
8288
static void SetVerboseLogging(bool enable);
8389

8490
#ifdef __OBJC__
85-
bool RegisterValueListener(const internal::QuerySpec& spec,
86-
ValueListener* listener,
87-
const ValueListenerCleanupData& cleanup_data);
91+
bool RegisterValueListener(
92+
const internal::QuerySpec& spec, ValueListener* listener,
93+
FIRCPPDatabaseQueryCallbackState* callback_state);
8894

8995
bool UnregisterValueListener(const internal::QuerySpec& spec,
9096
ValueListener* listener,
@@ -93,9 +99,9 @@ class DatabaseInternal {
9399
void UnregisterAllValueListeners(const internal::QuerySpec& spec,
94100
FIRDatabaseQuery* query_impl);
95101

96-
bool RegisterChildListener(const internal::QuerySpec& spec,
97-
ChildListener* listener,
98-
const ChildListenerCleanupData& cleanup_data);
102+
bool RegisterChildListener(
103+
const internal::QuerySpec& spec, ChildListener* listener,
104+
FIRCPPDatabaseQueryCallbackState* callback_state);
99105

100106
bool UnregisterChildListener(const internal::QuerySpec& spec,
101107
ChildListener* listener,
@@ -105,38 +111,18 @@ class DatabaseInternal {
105111
FIRDatabaseQuery* query_impl);
106112
#endif // __OBJC__
107113

108-
// Track a transient listener. If the database is deleted before the listener
109-
// finishes, it should discard its pointers.
110-
SingleValueListener** AddSingleValueListener(SingleValueListener* listener) {
114+
// Track a transient listener.
115+
void AddSingleValueListener(ValueListener* listener) {
111116
MutexLock lock(listener_mutex_);
112-
// If the listener is already being tracked, just return the existing
113-
// listener holder.
114-
for (auto i = single_value_listeners_.begin();
115-
i != single_value_listeners_.end(); ++i) {
116-
SingleValueListener** listener_holder = *i;
117-
if (*listener_holder == listener) {
118-
return listener_holder;
119-
}
120-
}
121-
// If the listener was not found, register create a new holder and return
122-
// it.
123-
SingleValueListener** holder = new SingleValueListener*(listener);
124-
single_value_listeners_.insert(holder);
125-
return holder;
117+
single_value_listeners_.insert(listener);
126118
}
127119

128-
// Finish tracking a transient listener. If the database is deleted before the
129-
// listener finishes, it should discard its pointers.
130-
void RemoveSingleValueListener(SingleValueListener* listener) {
120+
// Finish tracking a transient listener.
121+
void RemoveSingleValueListener(ValueListener* listener) {
131122
MutexLock lock(listener_mutex_);
132-
for (auto i = single_value_listeners_.begin();
133-
i != single_value_listeners_.end(); ++i) {
134-
SingleValueListener** listener_holder = *i;
135-
if (*listener_holder == listener) {
136-
single_value_listeners_.erase(i);
137-
return;
138-
}
139-
}
123+
auto it = single_value_listeners_.find(listener);
124+
if (it == single_value_listeners_.end()) return;
125+
single_value_listeners_.erase(it);
140126
}
141127

142128
FutureManager& future_manager() { return future_manager_; }
@@ -151,9 +137,17 @@ class DatabaseInternal {
151137
// The url that was passed to the constructor.
152138
const std::string& constructor_url() const { return constructor_url_; }
153139

140+
#ifdef __OBJC__
141+
// Guard access to C++ objects referenced by
142+
// FIRCPPDatabaseQueryCallbackStatePointer.
143+
NSRecursiveLock* query_lock() const {
144+
return query_lock_->ptr;
145+
}
146+
#endif // __OBJC__
147+
154148
private:
155149
#ifdef __OBJC__
156-
FIRDatabase* _Nonnull impl() const { return impl_->ptr; }
150+
FIRDatabase* impl() const { return impl_->ptr; }
157151
#endif // __OBJC__
158152

159153
// The firebase::App that this Database was created with.
@@ -162,18 +156,22 @@ class DatabaseInternal {
162156
// Object lifetime managed by Objective C ARC.
163157
UniquePtr<FIRDatabasePointer> impl_;
164158

159+
// Lock used to guard access to C++ objects referenced by FIRDatabaseQuery
160+
// callbacks.
161+
UniquePtr<NSRecursiveLockPointer> query_lock_;
162+
165163
// For registering listeners.
166164
Mutex listener_mutex_;
167165

168166
// Listeners indexed by QuerySpec.
169167
ListenerCollection<ValueListener> value_listeners_by_query_;
170168
ListenerCollection<ChildListener> child_listeners_by_query_;
171169

172-
std::map<ValueListener*, ValueListenerCleanupData>
170+
std::map<ValueListener*, FIRCPPDatabaseQueryCallbackStatePointer>
173171
cleanup_value_listener_lookup_;
174-
std::map<ChildListener*, ChildListenerCleanupData>
172+
std::map<ChildListener*, FIRCPPDatabaseQueryCallbackStatePointer>
175173
cleanup_child_listener_lookup_;
176-
std::set<SingleValueListener**> single_value_listeners_;
174+
std::set<ValueListener*> single_value_listeners_;
177175

178176
FutureManager future_manager_;
179177

@@ -184,6 +182,8 @@ class DatabaseInternal {
184182
std::string constructor_url_;
185183
};
186184

185+
#pragma clang assume_nonnull end
186+
187187
} // namespace internal
188188
} // namespace database
189189
} // namespace firebase

database/src/ios/database_ios.mm

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
@try {
3131
impl_.reset(new FIRDatabasePointer(
3232
[FIRDatabase databaseForApp:static_cast<FIRAppPointer*>(app->data_)->ptr]));
33+
query_lock_.reset(new NSRecursiveLockPointer([[NSRecursiveLock alloc] init]));
3334
}
3435
@catch (NSException* exception) {
3536
LogError(
@@ -43,6 +44,7 @@
4344
@try {
4445
impl_.reset(new FIRDatabasePointer(
4546
[FIRDatabase databaseForApp:static_cast<FIRAppPointer*>(app->data_)->ptr URL:@(url)]));
47+
query_lock_.reset(new NSRecursiveLockPointer([[NSRecursiveLock alloc] init]));
4648
}
4749
@catch (NSException* exception) {
4850
LogError(
@@ -57,12 +59,12 @@
5759
// If there are any pending listeners, delete their pointers.
5860
{
5961
MutexLock lock(listener_mutex_);
60-
for (auto i = single_value_listeners_.begin(); i != single_value_listeners_.end(); ++i) {
61-
SingleValueListener** listener_holder = *i;
62-
delete *listener_holder;
63-
*listener_holder = nullptr;
62+
while (single_value_listeners_.begin() != single_value_listeners_.end()) {
63+
auto it = single_value_listeners_.begin();
64+
auto* listener = *it;
65+
single_value_listeners_.erase(it);
66+
delete listener;
6467
}
65-
single_value_listeners_.clear();
6668
}
6769
}
6870

@@ -108,12 +110,14 @@ new DatabaseReferenceInternal(const_cast<DatabaseInternal*>(this),
108110

109111
bool DatabaseInternal::RegisterValueListener(
110112
const internal::QuerySpec& spec, ValueListener* listener,
111-
const ValueListenerCleanupData& cleanup_data) {
113+
FIRCPPDatabaseQueryCallbackState* callback_state) {
112114
MutexLock lock(listener_mutex_);
113115
if (value_listeners_by_query_.Register(spec, listener)) {
114116
auto found = cleanup_value_listener_lookup_.find(listener);
115117
if (found == cleanup_value_listener_lookup_.end()) {
116-
cleanup_value_listener_lookup_.insert(std::make_pair(listener, cleanup_data));
118+
cleanup_value_listener_lookup_.insert(std::make_pair(
119+
listener, FIRCPPDatabaseQueryCallbackStatePointer(
120+
callback_state)));
117121
}
118122
return true;
119123
}
@@ -127,8 +131,7 @@ new DatabaseReferenceInternal(const_cast<DatabaseInternal*>(this),
127131
if (value_listeners_by_query_.Unregister(spec, listener)) {
128132
auto found = cleanup_value_listener_lookup_.find(listener);
129133
if (found != cleanup_value_listener_lookup_.end()) {
130-
ValueListenerCleanupData& cleanup_data = found->second;
131-
[query_impl removeObserverWithHandle: cleanup_data.observer_handle];
134+
[found->second.ptr removeAllObservers];
132135
cleanup_value_listener_lookup_.erase(found);
133136
}
134137
return true;
@@ -146,13 +149,16 @@ new DatabaseReferenceInternal(const_cast<DatabaseInternal*>(this),
146149
}
147150
}
148151

149-
bool DatabaseInternal::RegisterChildListener(const internal::QuerySpec& spec,
150-
ChildListener* listener, const ChildListenerCleanupData& cleanup_data) {
152+
bool DatabaseInternal::RegisterChildListener(
153+
const internal::QuerySpec& spec, ChildListener* listener,
154+
FIRCPPDatabaseQueryCallbackState* _Nonnull callback_state) {
151155
MutexLock lock(listener_mutex_);
152156
if (child_listeners_by_query_.Register(spec, listener)) {
153157
auto found = cleanup_child_listener_lookup_.find(listener);
154158
if (found == cleanup_child_listener_lookup_.end()) {
155-
cleanup_child_listener_lookup_.insert(std::make_pair(listener, cleanup_data));
159+
cleanup_child_listener_lookup_.insert(std::make_pair(
160+
listener, FIRCPPDatabaseQueryCallbackStatePointer(
161+
callback_state)));
156162
}
157163
return true;
158164
}
@@ -166,11 +172,7 @@ new DatabaseReferenceInternal(const_cast<DatabaseInternal*>(this),
166172
if (child_listeners_by_query_.Unregister(spec, listener)) {
167173
auto found = cleanup_child_listener_lookup_.find(listener);
168174
if (found != cleanup_child_listener_lookup_.end()) {
169-
ChildListenerCleanupData& cleanup_data = found->second;
170-
[query_impl removeObserverWithHandle: cleanup_data.child_added_handle];
171-
[query_impl removeObserverWithHandle: cleanup_data.child_changed_handle];
172-
[query_impl removeObserverWithHandle: cleanup_data.child_moved_handle];
173-
[query_impl removeObserverWithHandle: cleanup_data.child_removed_handle];
175+
[found->second.ptr removeAllObservers];
174176
cleanup_child_listener_lookup_.erase(found);
175177
}
176178
return true;

database/src/ios/database_reference_ios.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
#include "database/src/include/firebase/database/transaction.h"
2828
#include "database/src/ios/query_ios.h"
2929

30+
#ifdef __OBJC__
31+
#import "FIRDatabase.h"
32+
#endif // __OBJC__
33+
3034
namespace firebase {
3135
namespace database {
3236
namespace internal {
@@ -35,13 +39,16 @@ namespace internal {
3539
// wrapper around the FIRDatabaseReference Obj-C class.
3640
OBJ_C_PTR_WRAPPER(FIRDatabaseReference);
3741

42+
#pragma clang assume_nonnull begin
43+
3844
// The iOS implementation of the DatabaseReference class, which represents a
3945
// particular location in your Database and can be used for reading or writing
4046
// data to that Database location.
4147
class DatabaseReferenceInternal : public QueryInternal {
4248
public:
4349
explicit DatabaseReferenceInternal(
44-
DatabaseInternal* database, UniquePtr<FIRDatabaseReferencePointer> impl);
50+
DatabaseInternal* database,
51+
UniquePtr<FIRDatabaseReferencePointer> impl);
4552

4653
virtual ~DatabaseReferenceInternal();
4754

@@ -103,8 +110,10 @@ class DatabaseReferenceInternal : public QueryInternal {
103110
// Run a user-supplied callback function, possibly multiple times, to perform
104111
// an atomic transaction on the database.
105112
Future<DataSnapshot> RunTransaction(
106-
DoTransactionWithContext transaction_function, void* context,
107-
void (*delete_context)(void*), bool trigger_local_events = true);
113+
DoTransactionWithContext transaction_function,
114+
void* _Nullable context,
115+
void (* _Nullable delete_context)(void* _Nullable),
116+
bool trigger_local_events = true);
108117

109118
// Get the result of the most recent call to RunTransaction().
110119
Future<DataSnapshot> RunTransactionLastResult();
@@ -142,7 +151,7 @@ class DatabaseReferenceInternal : public QueryInternal {
142151

143152
// Get the disconnect handler, which controls what actions the server will
144153
// perform to this location's data when this client disconnects.
145-
DisconnectionHandler* OnDisconnect();
154+
DisconnectionHandler* _Nullable OnDisconnect();
146155

147156
// Manually disconnect Firebase Realtime Database from the server, and disable
148157
// automatic reconnection.
@@ -154,7 +163,7 @@ class DatabaseReferenceInternal : public QueryInternal {
154163

155164
protected:
156165
#ifdef __OBJC__
157-
FIRDatabaseReference* _Nonnull impl() const { return impl_->ptr; }
166+
FIRDatabaseReference* impl() const { return impl_->ptr; }
158167
#endif // __OBJC__
159168

160169
private:
@@ -164,7 +173,7 @@ class DatabaseReferenceInternal : public QueryInternal {
164173
// Object lifetime managed by Objective C ARC.
165174
UniquePtr<FIRDatabaseReferencePointer> impl_;
166175

167-
DisconnectionHandler* cached_disconnection_handler_;
176+
DisconnectionHandler* _Nullable cached_disconnection_handler_;
168177

169178
// The memory location of this member variable is used to look up our
170179
// ReferenceCountedFutureImpl. We can't use "this" because QueryInternal and
@@ -174,6 +183,8 @@ class DatabaseReferenceInternal : public QueryInternal {
174183
int future_api_id_;
175184
};
176185

186+
#pragma clang assume_nonnull end
187+
177188
} // namespace internal
178189
} // namespace database
179190
} // namespace firebase

database/src/ios/disconnection_ios.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ namespace firebase {
2929
namespace database {
3030
namespace internal {
3131

32+
#pragma clang assume_nonnull begin
33+
3234
class FIRDatabaseReferencePointer;
3335
class DatabaseInternal;
3436
class DatabaseReferenceInternal;
@@ -92,10 +94,11 @@ class DisconnectionHandlerInternal {
9294
friend class DatabaseReferenceInternal;
9395

9496
explicit DisconnectionHandlerInternal(
95-
DatabaseInternal* database, UniquePtr<FIRDatabaseReferencePointer> impl);
97+
DatabaseInternal* database,
98+
UniquePtr<FIRDatabaseReferencePointer> impl);
9699

97100
#ifdef __OBJC__
98-
FIRDatabaseReference* _Nonnull impl() const;
101+
FIRDatabaseReference* impl() const;
99102
#endif // __OBJC__
100103

101104
// Get the Future for the DatabaseReferenceInternal.
@@ -106,6 +109,8 @@ class DisconnectionHandlerInternal {
106109
UniquePtr<FIRDatabaseReferencePointer> impl_;
107110
};
108111

112+
#pragma clang assume_nonnull end
113+
109114
} // namespace internal
110115
} // namespace database
111116
} // namespace firebase

database/src/ios/mutable_data_ios.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ namespace internal {
3333
// wrapper around the FIRMutableData Obj-C class.
3434
OBJ_C_PTR_WRAPPER(FIRMutableData);
3535

36+
#pragma clang assume_nonnull begin
37+
3638
class DatabaseReferenceInternal;
3739

3840
// The iOS implementation of MutableData, which encapsulates the data and
@@ -87,13 +89,15 @@ class MutableDataInternal {
8789
UniquePtr<FIRMutableDataPointer> impl);
8890

8991
#ifdef __OBJC__
90-
FIRMutableData* _Nonnull impl() const { return impl_->ptr; }
92+
FIRMutableData* impl() const { return impl_->ptr; }
9193
#endif // __OBJC__
9294

9395
DatabaseInternal* db_;
9496
UniquePtr<FIRMutableDataPointer> impl_;
9597
};
9698

99+
#pragma clang assume_nonnull end
100+
97101
} // namespace internal
98102
} // namespace database
99103
} // namespace firebase

0 commit comments

Comments
 (0)