Skip to content

Commit 6712b1e

Browse files
authored
SWIFT-294, SWIFT-394 Fix memory leaks (#237)
1 parent b789fcd commit 6712b1e

File tree

8 files changed

+65
-37
lines changed

8 files changed

+65
-37
lines changed

Sources/MongoSwift/APM.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public struct CommandStartedEvent: MongoEvent, InitializableFromOpaquePointer {
4848

4949
/// Initializes a CommandStartedEvent from an OpaquePointer to a mongoc_apm_command_started_t
5050
fileprivate init(_ event: OpaquePointer) {
51-
// swiftlint:disable:next force_unwrapping - documented as always returning a value.
52-
self.command = Document(fromPointer: mongoc_apm_command_started_get_command(event)!)
51+
// we have to copy because libmongoc owns the pointer.
52+
self.command = Document(copying: mongoc_apm_command_started_get_command(event))
5353
self.databaseName = String(cString: mongoc_apm_command_started_get_database_name(event))
5454
self.commandName = String(cString: mongoc_apm_command_started_get_command_name(event))
5555
self.requestId = mongoc_apm_command_started_get_request_id(event)
@@ -89,8 +89,8 @@ public struct CommandSucceededEvent: MongoEvent, InitializableFromOpaquePointer
8989
/// Initializes a CommandSucceededEvent from an OpaquePointer to a mongoc_apm_command_succeeded_t
9090
fileprivate init(_ event: OpaquePointer) {
9191
self.duration = mongoc_apm_command_succeeded_get_duration(event)
92-
// swiftlint:disable:next force_unwrapping - documented as always returning a value.
93-
self.reply = Document(fromPointer: mongoc_apm_command_succeeded_get_reply(event)!)
92+
// we have to copy because libmongoc owns the pointer.
93+
self.reply = Document(copying: mongoc_apm_command_succeeded_get_reply(event))
9494
self.commandName = String(cString: mongoc_apm_command_succeeded_get_command_name(event))
9595
self.requestId = mongoc_apm_command_succeeded_get_request_id(event)
9696
self.operationId = mongoc_apm_command_succeeded_get_operation_id(event)
@@ -319,8 +319,8 @@ public struct ServerHeartbeatSucceededEvent: MongoEvent, InitializableFromOpaque
319319
/// Initializes a ServerHeartbeatSucceededEvent from an OpaquePointer to a mongoc_apm_server_heartbeat_succeeded_t
320320
fileprivate init(_ event: OpaquePointer) {
321321
self.duration = mongoc_apm_server_heartbeat_succeeded_get_duration(event)
322-
// swiftlint:disable:next force_unwrapping - documented as always returning a value.
323-
self.reply = Document(fromPointer: mongoc_apm_server_heartbeat_succeeded_get_reply(event)!)
322+
// we have to copy because libmongoc owns the pointer.
323+
self.reply = Document(copying: mongoc_apm_server_heartbeat_succeeded_get_reply(event))
324324
self.connectionId = ConnectionId(mongoc_apm_server_heartbeat_succeeded_get_host(event))
325325
}
326326
}

Sources/MongoSwift/BSON/BSONValue.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ extension Array: BSONValue {
127127
throw RuntimeError.internalError(message: "Failed to create an Array from iterator")
128128
}
129129

130-
let arrDoc = Document(fromPointer: arrayData)
130+
let arrDoc = Document(stealing: arrayData)
131131

132132
guard let arr = arrDoc.values as? Array else {
133133
fatalError("Failed to cast values for document \(arrDoc) to array")
@@ -658,7 +658,7 @@ public struct CodeWithScope: BSONValue, Equatable, Codable {
658658
guard let scopeData = bson_new_from_data(scopePointer.pointee, Int(scopeLength)) else {
659659
throw RuntimeError.internalError(message: "Failed to create a bson_t from scope data")
660660
}
661-
let scopeDoc = Document(fromPointer: scopeData)
661+
let scopeDoc = Document(stealing: scopeData)
662662

663663
return self.init(code: code, scope: scopeDoc)
664664
}

Sources/MongoSwift/BSON/Document.swift

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,22 @@ public class DocumentStorage {
2020
return Int(bson_count_keys(self.pointer))
2121
}
2222

23+
/// Initializes a new, empty `DocumentStorage`.
2324
internal init() {
2425
self.pointer = bson_new()
2526
}
2627

27-
internal init(fromPointer pointer: BSONPointer) {
28+
/// Initializes a new `DocumentStorage` by copying over the data from the provided `bson_t`.
29+
internal init(copying pointer: BSONPointer) {
2830
self.pointer = bson_copy(pointer)
2931
}
3032

33+
/// Initializes a new `DocumentStorage` that uses the provided `bson_t` as its backing storage.
34+
/// The newly created instance will handle cleaning up the pointer upon deinitialization.
35+
internal init(stealing pointer: MutableBSONPointer) {
36+
self.pointer = pointer
37+
}
38+
3139
/// Cleans up internal state.
3240
deinit {
3341
guard let pointer = self.pointer else {
@@ -57,17 +65,30 @@ extension Document {
5765
}
5866

5967
/**
60-
* Initializes a `Document` from a pointer to a bson_t. Uses a copy
61-
* of `bsonData`, so the caller is responsible for freeing the original
62-
* memory.
68+
* Initializes a `Document` from a pointer to a `bson_t` by making a copy of the data. The `bson_t`'s owner is
69+
* responsible for freeing the original.
70+
*
71+
* - Parameters:
72+
* - pointer: a BSONPointer
73+
*
74+
* - Returns: a new `Document`
75+
*/
76+
internal init(copying pointer: BSONPointer) {
77+
self.storage = DocumentStorage(copying: pointer)
78+
self.count = self.storage.count
79+
}
80+
81+
/**
82+
* Initializes a `Document` from a pointer to a `bson_t`, "stealing" the `bson_t` to use for underlying storage/
83+
* The `bson_t` must not be modified or freed by others after it is used here/
6384
*
6485
* - Parameters:
65-
* - fromPointer: a BSONPointer
86+
* - pointer: a MutableBSONPointer
6687
*
6788
* - Returns: a new `Document`
6889
*/
69-
internal init(fromPointer pointer: BSONPointer) {
70-
self.storage = DocumentStorage(fromPointer: pointer)
90+
internal init(stealing pointer: MutableBSONPointer) {
91+
self.storage = DocumentStorage(stealing: pointer)
7192
self.count = self.storage.count
7293
}
7394

@@ -232,7 +253,7 @@ extension Document {
232253
*/
233254
private mutating func copyStorageIfRequired() {
234255
if !isKnownUniquelyReferenced(&self.storage) {
235-
self.storage = DocumentStorage(fromPointer: self.data)
256+
self.storage = DocumentStorage(copying: self.data)
236257
}
237258
}
238259

@@ -319,7 +340,7 @@ extension Document {
319340
* - A `UserError.invalidArgumentError` if the data passed in is invalid JSON.
320341
*/
321342
public init(fromJSON: Data) throws {
322-
self.storage = DocumentStorage(fromPointer: try fromJSON.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
343+
self.storage = DocumentStorage(stealing: try fromJSON.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
323344
var error = bson_error_t()
324345
guard let bson = bson_new_from_json(bytes, fromJSON.count, &error) else {
325346
if error.domain == BSON_ERROR_JSON {
@@ -328,11 +349,7 @@ extension Document {
328349
throw RuntimeError.internalError(message: toErrorString(error))
329350
}
330351

331-
#if compiler(>=5.0)
332352
return bson
333-
#else
334-
return UnsafePointer(bson)
335-
#endif
336353
})
337354
self.count = self.storage.count
338355
}
@@ -348,7 +365,7 @@ extension Document {
348365

349366
/// Constructs a `Document` from raw BSON `Data`.
350367
public init(fromBSON: Data) {
351-
self.storage = DocumentStorage(fromPointer: fromBSON.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
368+
self.storage = DocumentStorage(stealing: fromBSON.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
352369
bson_new_from_data(bytes, fromBSON.count)
353370
})
354371
self.count = self.storage.count
@@ -456,7 +473,7 @@ extension Document: BSONValue {
456473
throw RuntimeError.internalError(message: "Failed to create a Document from iterator")
457474
}
458475

459-
return self.init(fromPointer: docData)
476+
return self.init(stealing: docData)
460477
}
461478
}
462479
}

Sources/MongoSwift/MongoClient.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ public class MongoClient {
168168
guard let uri = mongoc_uri_new_with_error(connectionString, &error) else {
169169
throw parseMongocError(error)
170170
}
171+
defer { mongoc_uri_destroy(uri) }
171172

172173
// if retryWrites is specified, set it on the uri (libmongoc does not provide api for setting it on the client).
173174
if let rw = options?.retryWrites {

Sources/MongoSwift/MongoCursor.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ public class MongoCursor<T: Codable>: Sequence, IteratorProtocol {
8080
}
8181

8282
var replyPtr = UnsafeMutablePointer<BSONPointer?>.allocate(capacity: 1)
83-
defer { replyPtr.deallocate() }
83+
defer {
84+
replyPtr.deinitialize(count: 1)
85+
replyPtr.deallocate()
86+
}
8487

8588
var error = bson_error_t()
8689
guard mongoc_cursor_error_document(self._cursor, &error, replyPtr) else {
@@ -90,7 +93,8 @@ public class MongoCursor<T: Codable>: Sequence, IteratorProtocol {
9093
// If a reply is present, it implies the error occurred on the server. This *should* always be a commandError,
9194
// but we will still parse the mongoc error to cover all cases.
9295
if let docPtr = replyPtr.pointee {
93-
let reply = Document(fromPointer: docPtr)
96+
// we have to copy because libmongoc owns the pointer.
97+
let reply = Document(copying: docPtr)
9498
return parseMongocError(error, errorLabels: reply["errorLabels"] as? [String])
9599
}
96100

@@ -115,9 +119,13 @@ public class MongoCursor<T: Codable>: Sequence, IteratorProtocol {
115119
guard mongoc_cursor_next(self._cursor, out) else {
116120
return nil
117121
}
118-
// swiftlint:disable:next force_unwrapping - if mongoc_cursor_next returned `true`, this is filled out.
119-
let doc = Document(fromPointer: out.pointee!)
120122

123+
guard let pointee = out.pointee else {
124+
fatalError("mongoc_cursor_next returned true, but document is nil")
125+
}
126+
127+
// we have to copy because libmongoc owns the pointer.
128+
let doc = Document(copying: pointee)
121129
do {
122130
let outDoc = try self.decoder.decode(T.self, from: doc)
123131
self.swiftError = nil

Sources/MongoSwift/ReadPreference.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public final class ReadPreference {
7070
guard let bson = mongoc_read_prefs_get_tags(self._readPreference) else {
7171
fatalError("Failed to retrieve read preference tags")
7272
}
73-
74-
let wrapped = Document(fromPointer: bson)
73+
// we have to copy because libmongoc owns the pointer.
74+
let wrapped = Document(copying: bson)
7575

7676
// swiftlint:disable:next force_cast
7777
return wrapped.values as! [Document]

Sources/MongoSwift/SDAM.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ public struct ServerDescription {
171171
internal init(_ description: OpaquePointer) {
172172
self.connectionId = ConnectionId(mongoc_server_description_host(description))
173173
self.roundTripTime = mongoc_server_description_round_trip_time(description)
174-
// swiftlint:disable:next force_unwrapping - documented as always returning a value.
175-
let isMaster = Document(fromPointer: mongoc_server_description_ismaster(description)!)
174+
// we have to copy because libmongoc owns the pointer.
175+
let isMaster = Document(copying: mongoc_server_description_ismaster(description))
176176
self.parseIsMaster(isMaster)
177177

178178
let serverType = String(cString: mongoc_server_description_type(description))
@@ -250,6 +250,8 @@ public struct TopologyDescription {
250250

251251
var size = size_t()
252252
let serverData = mongoc_topology_description_get_servers(description, &size)
253+
defer { mongoc_server_descriptions_destroy_all(serverData, size) }
254+
253255
let buffer = UnsafeBufferPointer(start: serverData, count: size)
254256
if size > 0 {
255257
// swiftlint:disable:next force_unwrapping - documented as always returning a value.

Tests/MongoSwiftTests/DocumentTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ final class DocumentTests: MongoSwiftTestCase {
481481
// test replacing `Overwritable` types with values of their own type
482482
func testOverwritable() throws {
483483
// make a deep copy so we start off with uniquely referenced storage
484-
var doc = Document(fromPointer: DocumentTests.overwritables.data)
484+
var doc = Document(copying: DocumentTests.overwritables.data)
485485

486486
// save a reference to original bson_t so we can verify it doesn't change
487487
let pointer = doc.data
@@ -555,7 +555,7 @@ final class DocumentTests: MongoSwiftTestCase {
555555
// test replacing some of the non-Overwritable types with values of their own types
556556
func testNonOverwritable() throws {
557557
// make a deep copy so we start off with uniquely referenced storage
558-
var doc = Document(fromPointer: DocumentTests.nonOverwritables.data)
558+
var doc = Document(copying: DocumentTests.nonOverwritables.data)
559559

560560
// save a reference to original bson_t so we can verify it changes
561561
var pointer = doc.data
@@ -578,7 +578,7 @@ final class DocumentTests: MongoSwiftTestCase {
578578
// test replacing both overwritable and nonoverwritable values with values of different types
579579
func testReplaceValueWithNewType() throws {
580580
// make a deep copy so we start off with uniquely referenced storage
581-
var overwritableDoc = Document(fromPointer: DocumentTests.overwritables.data)
581+
var overwritableDoc = Document(copying: DocumentTests.overwritables.data)
582582

583583
// save a reference to original bson_t so we can verify it changes
584584
var overwritablePointer = overwritableDoc.data
@@ -613,7 +613,7 @@ final class DocumentTests: MongoSwiftTestCase {
613613
]))
614614

615615
// make a deep copy so we start off with uniquely referenced storage
616-
var nonOverwritableDoc = Document(fromPointer: DocumentTests.nonOverwritables.data)
616+
var nonOverwritableDoc = Document(copying: DocumentTests.nonOverwritables.data)
617617

618618
// save a reference to original bson_t so we can verify it changes
619619
var nonOverwritablePointer = nonOverwritableDoc.data
@@ -631,7 +631,7 @@ final class DocumentTests: MongoSwiftTestCase {
631631

632632
// test setting both overwritable and nonoverwritable values to nil
633633
func testReplaceValueWithNil() throws {
634-
var overwritableDoc = Document(fromPointer: DocumentTests.overwritables.data)
634+
var overwritableDoc = Document(copying: DocumentTests.overwritables.data)
635635
var overwritablePointer = overwritableDoc.data
636636

637637
["double", "int32", "int64", "bool", "decimal", "oid", "timestamp", "datetime"].forEach {
@@ -641,7 +641,7 @@ final class DocumentTests: MongoSwiftTestCase {
641641
overwritablePointer = overwritableDoc.data
642642
}
643643

644-
var nonOverwritableDoc = Document(fromPointer: DocumentTests.nonOverwritables.data)
644+
var nonOverwritableDoc = Document(copying: DocumentTests.nonOverwritables.data)
645645
var nonOverwritablePointer = nonOverwritableDoc.data
646646

647647
["string", "doc", "arr"].forEach {

0 commit comments

Comments
 (0)