Skip to content

Commit 210bfc2

Browse files
var-consta-maurice
authored andcommitted
Fix most Unity leaks caused by incorrect SWIG configuration.
There were two major sources of leaks: - SWIG was configured to ignore destructors of the public C++ classes; consequently, all of them were leaked; - SWIG was leaking the standard containers (and all their elements). The former problem is solved by fixing the SWIG configuration (as outlined in https://docs.google.com/document/d/1qqYXGzbB-01l00Mv-dGbGRXZn9XCAHjGtwstgj31R1Q). The intended SWIG way to use the standard library containers is to instantiate them using the `%template` directive. Unfortunately, this runs in a few problems in our build environment: - `swig_post_process.py` script changes visibility of any entity containing `Internal` in its name to `internal` (see https://cs.corp.google.com/piper///depot/google3/firebase/app/client/unity/swig_post_process.py?rcl=267221433&l=438). This breaks the C# code that SWIG generates for `std::vector`; - the older version of SWIG we're using doesn't support `std::unordered_map`. To work around this, C++ wrapper functions and classes are used to ensure that `std::vector` and `std::unordered_map` objects are never actually created in the C# layer. For example, the C# code would create a `FieldValueMap` (which is a simple non-template class) which will get converted into the underlying `std::unordered_map` in the C++ layer. Running "Leaks" instrument in XCode gives the following numbers when running a somewhat-outdated version of the Unity testapp (containing 29 tests): - ~7.1K leaks baseline; - ~4.5K leaks when enabling destructors for Firestore public C++ classes; - 69 leaks with this CL, a 99% reduction. Fixing the remaining leaks will be done in a follow-up. PiperOrigin-RevId: 314847898
1 parent 8d92e73 commit 210bfc2

File tree

2 files changed

+247
-66
lines changed

2 files changed

+247
-66
lines changed

firestore/src/include/firebase/csharp/map_helper.h

Lines changed: 204 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,217 @@
55
#include <vector>
66

77
#include "app/meta/move.h"
8+
#include "firebase/firestore/document_reference.h"
9+
#include "firebase/firestore/document_snapshot.h"
810
#include "firebase/firestore/field_path.h"
911
#include "firebase/firestore/field_value.h"
10-
#include "firebase/firestore/map_field_value.h"
12+
#include "firebase/firestore/set_options.h"
13+
#include "firebase/firestore/transaction.h"
14+
#include "firebase/firestore/write_batch.h"
1115

1216
namespace firebase {
1317
namespace firestore {
1418
namespace csharp {
1519

16-
// FieldValue is converted to FieldValueInternal of internal access, which
17-
// makes it impossible to use standard containers. What's more,
18-
// MapFieldValue type depends on compilation settings. Providing the helper
19-
// here, we provide a more transparent layer for C# calls.
20-
21-
// We cannot call std::vector<FieldValue>::foo() in C# since it is not
22-
// exposed via the .dll interface as SWIG cannot convert it. Here we
23-
// essentially define a function, which SWIG can convert and thus expose it
24-
// via the .dll. So C# code can call it.
25-
26-
// It is OK to re-construct a vector of strings since those strings can be
27-
// re-used by the C# code. This way is more clean than to expose an
28-
// iterator.
29-
inline std::vector<std::string> map_fv_keys(const MapFieldValue& self) {
30-
std::vector<std::string> result;
31-
result.reserve(self.size());
32-
for (const auto& kv : self) {
33-
result.push_back(kv.first);
34-
}
35-
return result;
36-
}
37-
38-
inline const FieldValue& map_fv_get(const MapFieldValue& self,
39-
const std::string& key) {
40-
return self.at(key);
41-
}
42-
43-
inline MapFieldValue map_fv_create() { return MapFieldValue{}; }
44-
45-
inline void map_fv_set(MapFieldValue* self, const std::string& key,
46-
FieldValue value) {
47-
// This could be either a move-assignment or a normal assignment.
48-
(*self)[key] = firebase::Move(value);
49-
}
50-
51-
inline MapFieldPathValue map_fpv_create() { return MapFieldPathValue{}; }
52-
53-
inline void map_set(MapFieldPathValue* self, FieldPath key, FieldValue value) {
54-
// These could be either move or copy.
55-
(*self)[firebase::Move(key)] = firebase::Move(value);
56-
}
20+
// Simple wrappers to avoid exposing C++ standard library containers to SWIG.
21+
//
22+
// While it's normally possible to work with standard library containers in SWIG
23+
// (by instantiating them for each template type used via the `%template`
24+
// directive), issues in the build environment make that approach too
25+
// complicated to be worth it. Instead, use simple wrappers and make sure the
26+
// underlying containers are never exposed to C#.
27+
//
28+
// Note: most of the time, these classes should be declared with a `using`
29+
// statement to ensure predictable lifetime of the object when dealing with
30+
// iterators or unsafe views.
31+
32+
// Wraps `MapFieldValue` for use in C# code.
33+
class FieldValueMap {
34+
public:
35+
FieldValueMap() = default;
36+
explicit FieldValueMap(const FieldValue& value)
37+
: container_(value.map_value()) {}
38+
39+
std::size_t Size() const { return container_.size(); }
40+
41+
// The returned reference is only valid as long as this `FieldValueMap` is
42+
// valid. In C#, declare the map with a `using` statement to ensure its
43+
// lifetime exceeds the lifetime of the reference.
44+
const FieldValue& GetUnsafeView(const std::string& key) const {
45+
auto found = container_.find(key);
46+
if (found != container_.end()) {
47+
return found->second;
48+
}
49+
50+
return InvalidFieldValue();
51+
}
52+
53+
FieldValue GetCopy(const std::string& key) const {
54+
// Note: this is safe because the reference returned by `GetUnsafeView` is
55+
// copied into the return value.
56+
return GetUnsafeView(key);
57+
}
58+
59+
void Insert(const std::string& key, const FieldValue& value) {
60+
container_[key] = value;
61+
}
62+
63+
class FieldValueMapIterator {
64+
public:
65+
bool HasMore() const { return cur_ != end_; }
66+
67+
void Advance() { ++cur_; }
68+
69+
const std::string& UnsafeKeyView() const { return cur_->first; }
70+
const FieldValue& UnsafeValueView() const { return cur_->second; }
71+
72+
std::string KeyCopy() const { return cur_->first; }
73+
FieldValue ValueCopy() const { return cur_->second; }
74+
75+
private:
76+
friend class FieldValueMap;
77+
explicit FieldValueMapIterator(const FieldValueMap& wrapper)
78+
: cur_{wrapper.Unwrap().begin()}, end_{wrapper.Unwrap().end()} {}
79+
80+
MapFieldValue::const_iterator cur_, end_;
81+
};
82+
83+
FieldValueMapIterator Iterator() const {
84+
return FieldValueMapIterator(*this);
85+
}
86+
87+
FieldValue ToFieldValue() const { return FieldValue::Map(Unwrap()); }
88+
89+
static FieldValue SnapshotToFieldValue(
90+
const DocumentSnapshot& snapshot,
91+
DocumentSnapshot::ServerTimestampBehavior stb) {
92+
return FieldValue::Map(snapshot.GetData(stb));
93+
}
94+
95+
static void TransactionUpdate(Transaction* transaction,
96+
const DocumentReference& doc,
97+
const FieldValue& field_value) {
98+
transaction->Update(doc, field_value.map_value());
99+
}
100+
101+
static void TransactionUpdate(Transaction* transaction,
102+
const DocumentReference& doc,
103+
const FieldValueMap& wrapper) {
104+
transaction->Update(doc, wrapper.Unwrap());
105+
}
106+
107+
static void WriteBatchUpdate(WriteBatch* batch, const DocumentReference& doc,
108+
const FieldValue& field_value) {
109+
batch->Update(doc, field_value.map_value());
110+
}
111+
112+
static void WriteBatchUpdate(WriteBatch* batch, const DocumentReference& doc,
113+
const FieldValueMap& wrapper) {
114+
batch->Update(doc, wrapper.Unwrap());
115+
}
116+
117+
static Future<void> DocumentReferenceSet(DocumentReference& doc,
118+
const FieldValue& field_value,
119+
const SetOptions& options) {
120+
return doc.Set(field_value.map_value(), options);
121+
}
122+
123+
static Future<void> DocumentReferenceUpdate(DocumentReference& doc,
124+
const FieldValue& field_value) {
125+
return doc.Update(field_value.map_value());
126+
}
127+
128+
private:
129+
const MapFieldValue& Unwrap() const { return container_; }
130+
131+
static const FieldValue& InvalidFieldValue() {
132+
static FieldValue value;
133+
return value;
134+
}
135+
136+
MapFieldValue container_;
137+
};
138+
139+
// Wraps `MapFieldPathValue` for use in C# code.
140+
// TODO(varconst): this should be a template instantiation, not a separate
141+
// class.
142+
class FieldPathValueMap {
143+
public:
144+
FieldPathValueMap() = default;
145+
146+
std::size_t Size() const { return container_.size(); }
147+
148+
// The returned reference is only valid as long as this `FieldPathValueMap` is
149+
// valid. In C#, declare the map with a `using` statement to ensure its
150+
// lifetime exceeds the lifetime of the reference.
151+
const FieldValue& GetUnsafeView(const FieldPath& key) const {
152+
auto found = container_.find(key);
153+
if (found != container_.end()) {
154+
return found->second;
155+
}
156+
157+
return InvalidFieldValue();
158+
}
159+
160+
FieldValue GetCopy(const FieldPath& key) const {
161+
// Note: this is safe because the reference returned by `GetUnsafeView` is
162+
// copied into the return value.
163+
return GetUnsafeView(key);
164+
}
165+
166+
void Insert(const FieldPath& key, const FieldValue& value) {
167+
container_[key] = value;
168+
}
169+
170+
class FieldPathValueMapIterator {
171+
public:
172+
bool HasMore() const { return cur_ != end_; }
173+
void Advance() { ++cur_; }
174+
175+
const FieldPath& UnsafeKeyView() const { return cur_->first; }
176+
const FieldValue& UnsafeValueView() const { return cur_->second; }
177+
178+
FieldPath KeyCopy() const { return cur_->first; }
179+
FieldValue ValueCopy() const { return cur_->second; }
180+
181+
private:
182+
friend class FieldPathValueMap;
183+
explicit FieldPathValueMapIterator(const FieldPathValueMap& wrapper)
184+
: cur_{wrapper.Unwrap().begin()}, end_{wrapper.Unwrap().end()} {}
185+
186+
MapFieldPathValue::const_iterator cur_, end_;
187+
};
188+
189+
FieldPathValueMapIterator Iterator() const {
190+
return FieldPathValueMapIterator(*this);
191+
}
192+
193+
static void TransactionUpdate(Transaction* transaction,
194+
const DocumentReference& doc,
195+
const FieldPathValueMap& wrapper) {
196+
transaction->Update(doc, wrapper.Unwrap());
197+
}
198+
199+
static void WriteBatchUpdate(WriteBatch* batch, const DocumentReference& doc,
200+
const FieldPathValueMap& wrapper) {
201+
batch->Update(doc, wrapper.Unwrap());
202+
}
203+
204+
static Future<void> DocumentReferenceUpdate(
205+
DocumentReference& doc, const FieldPathValueMap& wrapper) {
206+
return doc.Update(wrapper.Unwrap());
207+
}
208+
209+
private:
210+
const MapFieldPathValue& Unwrap() const { return container_; }
211+
212+
static const FieldValue& InvalidFieldValue() {
213+
static FieldValue value;
214+
return value;
215+
}
216+
217+
MapFieldPathValue container_;
218+
};
57219

58220
} // namespace csharp
59221
} // namespace firestore

firestore/src/include/firebase/csharp/vector_helper.h

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,55 @@ namespace firebase {
1111
namespace firestore {
1212
namespace csharp {
1313

14-
// FieldValue is converted to FieldValueInternal of internal access, which makes
15-
// it impossible to convert std::vector<FieldValue> with std_vector.i. Instead
16-
// of making a fully working std::vector<FieldValue>, we defined a few function
17-
// here.
14+
// Simple wrappers to avoid exposing C++ standard library containers to SWIG.
15+
//
16+
// While it's normally possible to work with standard library containers in SWIG
17+
// (by instantiating them for each template type used via the `%template`
18+
// directive), issues in the build environment make that approach too
19+
// complicated to be worth it. Instead, use simple wrappers and make sure the
20+
// underlying containers are never exposed to C#.
21+
//
22+
// Note: most of the time, these classes should be declared with a `using`
23+
// statement to ensure predictable lifetime of the object when dealing with
24+
// iterators or unsafe views.
1825

19-
// We cannot call std::vector<FieldValue>::foo() in C# since it is not exposed
20-
// via the .dll interface as SWIG cannot convert it. Here we essentially define
21-
// a function, which SWIG can convert and thus expose it via the .dll. So C#
22-
// code can call it.
26+
// Wraps `std::vector<FieldValue>` for use in C# code.
27+
class FieldValueVector {
28+
public:
29+
FieldValueVector() = default;
2330

24-
inline std::size_t vector_size(const std::vector<FieldValue>& self) {
25-
return self.size();
26-
}
31+
explicit FieldValueVector(const FieldValue& value)
32+
: container_(value.array_value()) {}
2733

28-
inline const FieldValue& vector_get(const std::vector<FieldValue>& self,
29-
std::size_t index) {
30-
return self[index];
31-
}
34+
std::size_t Size() const { return container_.size(); }
35+
36+
// The returned reference is only valid as long as this `FieldValueVector` is
37+
// valid. In C#, declare the vector with a `using` statement to ensure its
38+
// lifetime exceeds the lifetime of the reference.
39+
const FieldValue& GetUnsafeView(std::size_t i) const { return container_[i]; }
40+
41+
FieldValue GetCopy(std::size_t i) const { return container_[i]; }
3242

33-
inline std::vector<FieldValue> vector_fv_create(std::size_t size) {
34-
std::vector<FieldValue> result(size);
35-
return result;
43+
void PushBack(const FieldValue& value) {
44+
container_.push_back(value);
45+
}
46+
47+
private:
48+
friend FieldValue ArrayToFieldValue(const FieldValueVector& wrapper);
49+
50+
const std::vector<FieldValue>& Unwrap() const { return container_; }
51+
52+
std::vector<FieldValue> container_;
53+
};
54+
55+
FieldValue ArrayToFieldValue(const FieldValueVector& wrapper) {
56+
return FieldValue::Array(wrapper.Unwrap());
3657
}
3758

38-
// This is the only way to make it efficient for non-STLPort and also
39-
// STLPort-compatible.
40-
inline void vector_set(std::vector<FieldValue>* self, std::size_t index,
41-
FieldValue field_value) {
42-
// This could be either a move-assignment or a normal assignment.
43-
(*self)[index] = firebase::Move(field_value);
59+
// TODO(varconst): handle everything below in a similar way.
60+
61+
inline std::size_t vector_size(const std::vector<FieldValue>& self) {
62+
return self.size();
4463
}
4564

4665
// Similarly, DocumentSnapshot is converted to DocumentSnapshotInternal and we

0 commit comments

Comments
 (0)