Skip to content

Commit d172f00

Browse files
committed
Ensure the DLHandle passed to SourceKitD.init(dlhandle:path:pluginPaths:initialize:) gets closed if the initializer fails
Just something I noticed while looking through code. I am not aware of any issues caused by this. Also relax the `precondition` in `DLHandle.deinit` to a fault. If we do not close a `DLHandle`, that’s a bug but it’s not so bad that we should crash sourcekit-lsp for it.
1 parent 53407f5 commit d172f00

File tree

2 files changed

+46
-39
lines changed

2 files changed

+46
-39
lines changed

Sources/SourceKitD/SourceKitD.swift

+42-38
Original file line numberDiff line numberDiff line change
@@ -193,52 +193,56 @@ package actor SourceKitD {
193193
let dlopenModes: DLOpenFlags = [.lazy, .local, .first]
194194
#endif
195195
let dlhandle = try dlopen(path.filePath, mode: dlopenModes)
196-
do {
197-
try self.init(
198-
dlhandle: dlhandle,
199-
path: path,
200-
pluginPaths: pluginPaths,
201-
initialize: initialize
202-
)
203-
} catch {
204-
try? dlhandle.close()
205-
throw error
206-
}
196+
try self.init(
197+
dlhandle: dlhandle,
198+
path: path,
199+
pluginPaths: pluginPaths,
200+
initialize: initialize
201+
)
207202
}
208203

204+
/// Create a `SourceKitD` instance from an existing `DLHandle`. `SourceKitD` takes over ownership of the `DLHandler`
205+
/// and will close it when the `SourceKitD` instance gets deinitialized or if the initializer throws.
209206
package init(dlhandle: DLHandle, path: URL, pluginPaths: PluginPaths?, initialize: Bool) throws {
210-
self.path = path
211-
self.dylib = dlhandle
212-
let api = try sourcekitd_api_functions_t(dlhandle)
213-
self.api = api
214-
215-
// We load the plugin-related functions eagerly so the members are initialized and we don't have data races on first
216-
// access to eg. `pluginApi`. But if one of the functions is missing, we will only emit that error when that family
217-
// of functions is being used. For example, it is expected that the plugin functions are not available in
218-
// SourceKit-LSP.
219-
self.ideApiResult = Result(catching: { try sourcekitd_ide_api_functions_t(dlhandle) })
220-
self.pluginApiResult = Result(catching: { try sourcekitd_plugin_api_functions_t(dlhandle) })
221-
self.servicePluginApiResult = Result(catching: { try sourcekitd_service_plugin_api_functions_t(dlhandle) })
222-
223-
if let pluginPaths {
224-
api.register_plugin_path?(pluginPaths.clientPlugin.path, pluginPaths.servicePlugin.path)
225-
}
226-
if initialize {
227-
self.api.initialize()
228-
}
207+
do {
208+
self.path = path
209+
self.dylib = dlhandle
210+
let api = try sourcekitd_api_functions_t(dlhandle)
211+
self.api = api
212+
213+
// We load the plugin-related functions eagerly so the members are initialized and we don't have data races on first
214+
// access to eg. `pluginApi`. But if one of the functions is missing, we will only emit that error when that family
215+
// of functions is being used. For example, it is expected that the plugin functions are not available in
216+
// SourceKit-LSP.
217+
self.ideApiResult = Result(catching: { try sourcekitd_ide_api_functions_t(dlhandle) })
218+
self.pluginApiResult = Result(catching: { try sourcekitd_plugin_api_functions_t(dlhandle) })
219+
self.servicePluginApiResult = Result(catching: { try sourcekitd_service_plugin_api_functions_t(dlhandle) })
229220

230-
if initialize {
231-
self.api.set_notification_handler { [weak self] rawResponse in
232-
guard let self, let rawResponse else { return }
233-
let response = SKDResponse(rawResponse, sourcekitd: self)
234-
self.notificationHandlingQueue.async {
235-
let handlers = await self.notificationHandlers.compactMap(\.value)
221+
if let pluginPaths {
222+
api.register_plugin_path?(pluginPaths.clientPlugin.path, pluginPaths.servicePlugin.path)
223+
}
224+
if initialize {
225+
self.api.initialize()
226+
}
227+
228+
if initialize {
229+
self.api.set_notification_handler { [weak self] rawResponse in
230+
guard let self, let rawResponse else { return }
231+
let response = SKDResponse(rawResponse, sourcekitd: self)
232+
self.notificationHandlingQueue.async {
233+
let handlers = await self.notificationHandlers.compactMap(\.value)
236234

237-
for handler in handlers {
238-
handler.notification(response)
235+
for handler in handlers {
236+
handler.notification(response)
237+
}
239238
}
240239
}
241240
}
241+
} catch {
242+
orLog("Closing dlhandle after opening sourcekitd failed") {
243+
try? dlhandle.close()
244+
}
245+
throw error
242246
}
243247
}
244248

Sources/SourceKitD/dlopen.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import SKLogging
1314
import SwiftExtensions
1415

1516
#if os(Windows)
@@ -45,7 +46,9 @@ package final class DLHandle: Sendable {
4546
}
4647

4748
deinit {
48-
precondition(rawValue.value == nil, "DLHandle must be closed or explicitly leaked before destroying")
49+
if rawValue.value != nil {
50+
logger.fault("DLHandle must be closed or explicitly leaked before destroying")
51+
}
4952
}
5053

5154
/// The handle must not be used anymore after calling `close`.

0 commit comments

Comments
 (0)