Skip to content

Commit 2982d82

Browse files
committed
Make library more signal safe
- Switch to a preallocated array of pre-determined signal handlers - Use write(1) instead of Foundation - Switch from String to C-Strings
1 parent 32fee9f commit 2982d82

File tree

1 file changed

+47
-17
lines changed

1 file changed

+47
-17
lines changed

Sources/PrettyStackTrace/PrettyStackTrace.swift

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,41 @@ private struct TraceEntry: CustomStringConvertible {
3232
}
3333
}
3434

35-
var stderr = FileHandle.standardError
35+
// HACK: This array must be pre-allocated and contains functionally immortal
36+
// C-strings because String may allocate when passed to write(1).
37+
var registeredSignalInfo =
38+
UnsafeMutableBufferPointer<SigHandler>(start:
39+
UnsafeMutablePointer.allocate(capacity: killSigs.count),
40+
count: killSigs.count)
41+
var numRegisteredSignalInfo = 0
3642

3743
/// A class managing a stack of trace entries. When a particular thread gets
3844
/// a kill signal, this handler will dump all the entries in the tack trace and
3945
/// end the process.
4046
private class PrettyStackTraceManager {
47+
struct RawStackEntry {
48+
let data: UnsafeMutablePointer<Int8>
49+
let count: Int
50+
}
51+
4152
/// Keeps a stack of serialized trace entries in reverse order.
42-
/// - Note: This keeps strings, because it's not particularly safe to
53+
/// - Note: This keeps strings, because it's not safe to
4354
/// construct the strings in the signal handler directly.
44-
var stack = [Data]()
45-
let stackDumpMsgData = "Stack dump:\n".data(using: .utf8)!
55+
var stack = [RawStackEntry]()
56+
57+
private let stackDumpMsg: RawStackEntry
58+
init() {
59+
let msg = "Stack dump:\n"
60+
stackDumpMsg = RawStackEntry(data: strndup(msg, msg.count),
61+
count: msg.count)
62+
}
4663

4764
/// Pushes the description of a trace entry to the stack.
4865
func push(_ entry: TraceEntry) {
4966
let str = "\(entry.description)\n"
50-
stack.insert(str.data(using: .utf8)!, at: 0)
67+
let entry = RawStackEntry(data: strndup(str, str.count),
68+
count: str.count)
69+
stack.insert(entry, at: 0)
5170
}
5271

5372
/// Pops the latest trace entry off the stack.
@@ -59,9 +78,15 @@ private class PrettyStackTraceManager {
5978
/// Dumps the stack entries to standard error, starting with the most
6079
/// recent entry.
6180
func dump(_ signal: Int32) {
62-
stderr.write(stackDumpMsgData)
63-
for entry in stack {
64-
stderr.write(entry)
81+
write(STDERR_FILENO, stackDumpMsg.data, stackDumpMsg.count)
82+
let stackLimit = stack.count
83+
stack.withUnsafeBufferPointer { buffer in
84+
var i = 0
85+
while i < stackLimit {
86+
let bufItem = buffer[i]
87+
write(STDERR_FILENO, bufItem.data, bufItem.count)
88+
i += 1
89+
}
6590
}
6691
}
6792
}
@@ -112,38 +137,43 @@ struct SigHandler {
112137
var signalNumber: Int32
113138
}
114139

115-
/// The currently registered set of signal handlers.
116-
private var handlers = [SigHandler]()
117-
118140
/// Registers the pretty stack trace signal handlers.
119141
private func registerHandler(signal: Int32) {
120142
var newHandler = SigAction()
121-
newHandler.__sigaction_u.__sa_handler = {
143+
newHandler.__sigaction_u.__sa_handler = { signalNumber in
122144
unregisterHandlers()
123145

124146
// Unblock all potentially blocked kill signals
125147
var sigMask = sigset_t()
126148
sigfillset(&sigMask)
127149
sigprocmask(SIG_UNBLOCK, &sigMask, nil)
128150

129-
threadLocalHandler().dump($0)
130-
exit(-1)
151+
threadLocalHandler().dump(signalNumber)
152+
exit(signalNumber)
131153
}
132154
newHandler.sa_flags = SA_NODEFER | SA_RESETHAND | SA_ONSTACK
133155
sigemptyset(&newHandler.sa_mask)
134156

135157
var handler = SigAction()
136158
if sigaction(signal, &newHandler, &handler) != 0 {
137159
let sh = SigHandler(action: handler, signalNumber: signal)
138-
handlers.append(sh)
160+
registeredSignalInfo[numRegisteredSignalInfo] = sh
139161
}
140162
}
141163

142164
/// Unregisters all pretty stack trace signal handlers.
143165
private func unregisterHandlers() {
144-
while var handler = handlers.popLast() {
145-
sigaction(handler.signalNumber, &handler.action, nil)
166+
var i = 0
167+
while i < killSigs.count {
168+
sigaction(registeredSignalInfo[i].signalNumber,
169+
&registeredSignalInfo[i].action, nil)
170+
i += 1
146171
}
172+
173+
// HACK: Must leak the old registerdSignalInfo because we cannot safely
174+
// free inside a signal handler.
175+
// cannot: free(registeredSignalInfo)
176+
numRegisteredSignalInfo = 0
147177
}
148178

149179
/// A reference to the previous alternate stack, if any.

0 commit comments

Comments
 (0)