Skip to content

Commit 0ed232a

Browse files
authored
Merge pull request #81457 from mikeash/swift-inspect-corpse-leak
[swift-inspect] Fix corpse leaks when target doesn't have libswiftCore loaded.
2 parents a41919c + 9671d6b commit 0ed232a

File tree

3 files changed

+89
-37
lines changed

3 files changed

+89
-37
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// A helper struct that manages the cleanup of some external resource.
14+
/// The value is stored as an Optional, but presented as a non-optional.
15+
/// Accessing the value before one has been set is a runtime error. The cleanup
16+
/// function is called on the value if one has been set, when setting a new
17+
/// value or when the struct is destroyed.
18+
struct Cleanup<T>: ~Copyable {
19+
/// The underlying Optional storage for the value.
20+
private var _value: T?
21+
22+
/// The wrapped value. A value must be set before any value is read here.
23+
var value: T {
24+
get {
25+
_value!
26+
}
27+
set {
28+
if let _value {
29+
cleanup(_value)
30+
}
31+
_value = newValue
32+
}
33+
}
34+
35+
/// The function used to clean up the resource held by this struct.
36+
let cleanup: (T) -> Void
37+
38+
init(cleanup: @escaping (T) -> Void) {
39+
self._value = nil
40+
self.cleanup = cleanup
41+
}
42+
43+
deinit {
44+
if let _value {
45+
cleanup(_value)
46+
}
47+
}
48+
}

tools/swift-inspect/Sources/swift-inspect/DarwinRemoteProcess.swift

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,25 @@ internal final class DarwinRemoteProcess: RemoteProcess {
1919
public typealias ProcessIdentifier = pid_t
2020
public typealias ProcessHandle = task_t
2121

22-
private var task: task_t
22+
private var task = Cleanup<task_t> {
23+
// task_stop_peeking does nothing if we didn't task_start_peeking first, so
24+
// we can call it unconditionally.
25+
task_stop_peeking($0)
26+
mach_port_deallocate(mach_task_self_, $0)
27+
}
28+
2329
internal var processIdentifier: ProcessIdentifier
2430
internal lazy var processName = getProcessName(processId: processIdentifier) ?? "<unknown process>"
2531

26-
public var process: ProcessHandle { task }
27-
public private(set) var context: SwiftReflectionContextRef!
28-
private var symbolicator: CSSymbolicatorRef
32+
public var process: ProcessHandle { task.value }
33+
private var _context = Cleanup<SwiftReflectionContextRef> {
34+
swift_reflection_destroyReflectionContext($0)
35+
}
36+
public var context: SwiftReflectionContextRef! { _context.value }
37+
38+
private var symbolicator = Cleanup<CSSymbolicatorRef> {
39+
CSRelease($0)
40+
}
2941

3042
private var swiftCore: CSTypeRef
3143
private let swiftConcurrency: CSTypeRef
@@ -72,7 +84,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
7284
}
7385

7486
func read(address: swift_addr_t, size: Int) -> UnsafeRawPointer? {
75-
return task_peek(task, address, mach_vm_size_t(size))
87+
return task_peek(task.value, address, mach_vm_size_t(size))
7688
}
7789

7890
func getAddr(symbolName: String) -> swift_addr_t {
@@ -98,7 +110,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
98110
static var GetStringLength: GetStringLengthFunction {
99111
return { (context, address) in
100112
let process: DarwinRemoteProcess = DarwinRemoteProcess.fromOpaque(context!)
101-
if let str = task_peek_string(process.task, address) {
113+
if let str = task_peek_string(process.task.value, address) {
102114
return UInt64(strlen(str))
103115
}
104116
return 0
@@ -119,18 +131,19 @@ internal final class DarwinRemoteProcess: RemoteProcess {
119131

120132
init?(processId: ProcessIdentifier, forkCorpse: Bool) {
121133
processIdentifier = processId
122-
var task: task_t = task_t()
123-
let taskResult = task_for_pid(mach_task_self_, processId, &task)
134+
var processTask: task_t = task_t()
135+
let taskResult = task_for_pid(mach_task_self_, processId, &processTask)
124136
guard taskResult == KERN_SUCCESS else {
125137
print("unable to get task for pid \(processId): \(String(cString: mach_error_string(taskResult))) \(hex: taskResult)",
126138
to: &Std.err)
127139
return nil
128140
}
141+
self.task.value = processTask
129142

130143
// Consult with VMUProcInfo to determine if we should force forkCorpse.
131144
let forceForkCorpse: Bool
132145
if let procInfoClass = getVMUProcInfoClass() {
133-
let procInfo = procInfoClass.init(task: task)
146+
let procInfo = procInfoClass.init(task: self.task.value)
134147
forceForkCorpse = procInfo.shouldAnalyzeWithCorpse
135148
} else {
136149
// Default to not forcing forkCorpse.
@@ -141,11 +154,9 @@ internal final class DarwinRemoteProcess: RemoteProcess {
141154
var corpse = task_t()
142155
let maxRetry = 6
143156
for retry in 0..<maxRetry {
144-
let corpseResult = task_generate_corpse(task, &corpse)
157+
let corpseResult = task_generate_corpse(task.value, &corpse)
145158
if corpseResult == KERN_SUCCESS {
146-
task_stop_peeking(task)
147-
mach_port_deallocate(mach_task_self_, task)
148-
task = corpse
159+
task.value = corpse
149160
break
150161
}
151162
if corpseResult != KERN_RESOURCE_SHORTAGE || retry == maxRetry {
@@ -157,20 +168,19 @@ internal final class DarwinRemoteProcess: RemoteProcess {
157168
}
158169
}
159170

160-
self.task = task
171+
self.symbolicator.value = CSSymbolicatorCreateWithTask(self.task.value)
172+
173+
self.swiftCore = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
174+
self.symbolicator.value, "libswiftCore.dylib", kCSNow)
175+
self.swiftConcurrency = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
176+
self.symbolicator.value, "libswift_Concurrency.dylib", kCSNow)
161177

162-
self.symbolicator = CSSymbolicatorCreateWithTask(self.task)
163-
self.swiftCore =
164-
CSSymbolicatorGetSymbolOwnerWithNameAtTime(self.symbolicator,
165-
"libswiftCore.dylib", kCSNow)
166178
if CSIsNull(self.swiftCore) {
167179
print("pid \(processId) does not have libswiftCore.dylib loaded")
168180
return nil
169181
}
170182

171-
self.swiftConcurrency = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
172-
symbolicator, "libswift_Concurrency.dylib", kCSNow)
173-
_ = task_start_peeking(self.task)
183+
_ = task_start_peeking(self.task.value)
174184

175185
guard let context =
176186
swift_reflection_createReflectionContextWithDataLayout(self.toOpaqueRef(),
@@ -181,23 +191,17 @@ internal final class DarwinRemoteProcess: RemoteProcess {
181191
Self.GetSymbolAddress) else {
182192
return nil
183193
}
184-
self.context = context
194+
self._context.value = context
185195

186-
_ = CSSymbolicatorForeachSymbolOwnerAtTime(self.symbolicator, kCSNow, { owner in
196+
_ = CSSymbolicatorForeachSymbolOwnerAtTime(self.symbolicator.value, kCSNow, { owner in
187197
let address = CSSymbolOwnerGetBaseAddress(owner)
188198
_ = swift_reflection_addImage(self.context, address)
189199
})
190200
}
191201

192-
deinit {
193-
task_stop_peeking(self.task)
194-
CSRelease(self.symbolicator)
195-
mach_port_deallocate(mach_task_self_, self.task)
196-
}
197-
198202
func symbolicate(_ address: swift_addr_t) -> (module: String?, symbol: String?) {
199203
let symbol =
200-
CSSymbolicatorGetSymbolWithAddressAtTime(self.symbolicator, address, kCSNow)
204+
CSSymbolicatorGetSymbolWithAddressAtTime(self.symbolicator.value, address, kCSNow)
201205

202206
let module = CSSymbolGetSymbolOwner(symbol)
203207
return (CSSymbolOwnerGetName(module), CSSymbolGetName(symbol))
@@ -206,7 +210,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
206210
internal func iterateHeap(_ body: (swift_addr_t, UInt64) -> Void) {
207211
withoutActuallyEscaping(body) {
208212
withUnsafePointer(to: $0) {
209-
task_enumerate_malloc_blocks(self.task,
213+
task_enumerate_malloc_blocks(self.task.value,
210214
UnsafeMutableRawPointer(mutating: $0),
211215
CUnsignedInt(MALLOC_PTR_IN_USE_RANGE_TYPE),
212216
{ (task, context, type, ranges, count) in
@@ -264,14 +268,14 @@ extension DarwinRemoteProcess {
264268
}
265269

266270
private func getThreadInfos() -> [ThreadInfo] {
267-
guard let threads = PortList(task: self.task) else {
271+
guard let threads = PortList(task: self.task.value) else {
268272
return []
269273
}
270-
return threads.compactMap {
271-
guard let info = getThreadInfo(thread: $0) else {
274+
return threads.compactMap { t -> ThreadInfo? in
275+
guard let info = getThreadInfo(thread: t) else {
272276
return nil
273277
}
274-
guard let kernelObj = getKernelObject(task: mach_task_self_, port: $0) else {
278+
guard let kernelObj = getKernelObject(task: mach_task_self_, port: t) else {
275279
return nil
276280
}
277281
return ThreadInfo(threadID: info.thread_id,
@@ -328,7 +332,7 @@ extension DarwinRemoteProcess {
328332
}
329333

330334
internal func getThreadID(remotePort: thread_t) -> UInt64? {
331-
guard let remoteThreadObj = getKernelObject(task: self.task, port: remotePort) else {
335+
guard let remoteThreadObj = getKernelObject(task: self.task.value, port: remotePort) else {
332336
return nil
333337
}
334338
return threadInfos.first{ $0.kernelObject == remoteThreadObj }?.threadID

tools/swift-inspect/Sources/swift-inspect/Process.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ internal func getAllProcesses(options: UniversalOptions) -> [ProcessIdentifier]?
8080
}
8181
let newCount = bufferSize / kinfo_stride
8282
if count > newCount {
83-
buffer.dropLast(count - newCount)
83+
buffer.removeLast(count - newCount)
8484
}
8585
let sorted = buffer.sorted { first, second in
8686
first.kp_proc.p_pid > second.kp_proc.p_pid

0 commit comments

Comments
 (0)