|
| 1 | +/// PrettyStackTrace.swift |
| 2 | +/// |
| 3 | +/// Copyright 2018, The LLVMSwift Project. |
| 4 | +/// |
| 5 | +/// This project is released under the MIT license, a copy of which is |
| 6 | +/// available in the repository. |
| 7 | + |
| 8 | +import Foundation |
| 9 | + |
| 10 | +/// Represents an entry in a stack trace. Contains information necessary to |
| 11 | +/// reconstruct what was happening at the time this function was executed. |
| 12 | +private struct TraceEntry: CustomStringConvertible { |
| 13 | + /// A description, in gerund form, of what action was occurring at the time. |
| 14 | + /// For example, "typechecking a node". |
| 15 | + let action: String |
| 16 | + |
| 17 | + /// The function (from #function) that was being executed. |
| 18 | + let function: StaticString |
| 19 | + |
| 20 | + /// The line (from #line) that was being executed. |
| 21 | + let line: Int |
| 22 | + |
| 23 | + /// The file (from #file) that was being executed. |
| 24 | + let file: StaticString |
| 25 | + |
| 26 | + /// Constructs a string describing the entry, to be printed in order. |
| 27 | + var description: String { |
| 28 | + let base = URL(fileURLWithPath: file.description).lastPathComponent |
| 29 | + return """ |
| 30 | + -> While \(action) (func \(function), in file \(base), line \(line)) |
| 31 | + """ |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +var stderr = FileHandle.standardError |
| 36 | + |
| 37 | +/// A class managing a stack of trace entries. When a particular thread gets |
| 38 | +/// a kill signal, this handler will dump all the entries in the tack trace and |
| 39 | +/// end the process. |
| 40 | +private class PrettyStackTraceManager { |
| 41 | + /// Keeps a stack of serialized trace entries in reverse order. |
| 42 | + /// - Note: This keeps strings, because it's not particularly safe to |
| 43 | + /// construct the strings in the signal handler directly. |
| 44 | + var stack = [Data]() |
| 45 | + let stackDumpMsgData = "Stack dump:\n".data(using: .utf8)! |
| 46 | + |
| 47 | + /// Pushes the description of a trace entry to the stack. |
| 48 | + func push(_ entry: TraceEntry) { |
| 49 | + let str = "\(entry.description)\n" |
| 50 | + stack.insert(str.data(using: .utf8)!, at: 0) |
| 51 | + } |
| 52 | + |
| 53 | + /// Pops the latest trace entry off the stack. |
| 54 | + func pop() { |
| 55 | + guard !stack.isEmpty else { return } |
| 56 | + stack.removeFirst() |
| 57 | + } |
| 58 | + |
| 59 | + /// Dumps the stack entries to standard error, starting with the most |
| 60 | + /// recent entry. |
| 61 | + func dump(_ signal: Int32) { |
| 62 | + stderr.write(stackDumpMsgData) |
| 63 | + for entry in stack { |
| 64 | + stderr.write(entry) |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +/// Storage for a thread-local context key to get the thread local trace |
| 70 | +/// handler. |
| 71 | +private var __stackContextKey = pthread_key_t() |
| 72 | + |
| 73 | +/// Creates a key for a thread-local reference to a PrettyStackTraceHandler. |
| 74 | +private var stackContextKey: pthread_key_t = { |
| 75 | + pthread_key_create(&__stackContextKey) { ptr in |
| 76 | + let unmanaged = Unmanaged<PrettyStackTraceManager>.fromOpaque(ptr) |
| 77 | + unmanaged.release() |
| 78 | + } |
| 79 | + return __stackContextKey |
| 80 | +}() |
| 81 | + |
| 82 | +/// A thread-local reference to a PrettyStackTraceManager. |
| 83 | +/// The first time this is created, this will create the handler and register |
| 84 | +/// it with `pthread_setspecific`. |
| 85 | +private func threadLocalHandler() -> PrettyStackTraceManager { |
| 86 | + guard let specificPtr = pthread_getspecific(stackContextKey) else { |
| 87 | + let handler = PrettyStackTraceManager() |
| 88 | + let unmanaged = Unmanaged.passRetained(handler) |
| 89 | + pthread_setspecific(stackContextKey, unmanaged.toOpaque()) |
| 90 | + return handler |
| 91 | + } |
| 92 | + let unmanaged = Unmanaged<PrettyStackTraceManager>.fromOpaque(specificPtr) |
| 93 | + return unmanaged.takeUnretainedValue() |
| 94 | +} |
| 95 | + |
| 96 | +/// A set of signals that would normally kill the program. We handle these |
| 97 | +/// signals by dumping the pretty stack trace description. |
| 98 | +let killSigs = [ |
| 99 | + SIGILL, SIGABRT, SIGTRAP, SIGFPE, |
| 100 | + SIGBUS, SIGSEGV, SIGSYS, SIGQUIT |
| 101 | +] |
| 102 | + |
| 103 | +/// Needed because the type `sigaction` conflicts with the function `sigaction`. |
| 104 | +typealias SigAction = sigaction |
| 105 | + |
| 106 | +/// A wrapper that associates a signal handler with the number it handles. |
| 107 | +struct SigHandler { |
| 108 | + /// The signal action, called when the handler is called. |
| 109 | + var action: SigAction |
| 110 | + |
| 111 | + /// The signal number this will fire on. |
| 112 | + var signalNumber: Int32 |
| 113 | +} |
| 114 | + |
| 115 | +/// The currently registered set of signal handlers. |
| 116 | +private var handlers = [SigHandler]() |
| 117 | + |
| 118 | +/// Registers the pretty stack trace signal handlers. |
| 119 | +private func registerHandler(signal: Int32) { |
| 120 | + var newHandler = SigAction() |
| 121 | + newHandler.__sigaction_u.__sa_handler = { |
| 122 | + unregisterHandlers() |
| 123 | + |
| 124 | + // Unblock all potentially blocked kill signals |
| 125 | + var sigMask = sigset_t() |
| 126 | + sigfillset(&sigMask) |
| 127 | + sigprocmask(SIG_UNBLOCK, &sigMask, nil) |
| 128 | + |
| 129 | + threadLocalHandler().dump($0) |
| 130 | + exit(-1) |
| 131 | + } |
| 132 | + newHandler.sa_flags = SA_NODEFER | SA_RESETHAND | SA_ONSTACK |
| 133 | + sigemptyset(&newHandler.sa_mask) |
| 134 | + |
| 135 | + var handler = SigAction() |
| 136 | + if sigaction(signal, &newHandler, &handler) != 0 { |
| 137 | + let sh = SigHandler(action: handler, signalNumber: signal) |
| 138 | + handlers.append(sh) |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +/// Unregisters all pretty stack trace signal handlers. |
| 143 | +private func unregisterHandlers() { |
| 144 | + while var handler = handlers.popLast() { |
| 145 | + sigaction(handler.signalNumber, &handler.action, nil) |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +/// A reference to the previous alternate stack, if any. |
| 150 | +private var oldAltStack = stack_t() |
| 151 | + |
| 152 | +/// The current stack pointer for this alternate stack. |
| 153 | +private var newAltStackPointer: UnsafeMutableRawPointer? |
| 154 | + |
| 155 | +/// Sets up an alternate stack and registers all signal handlers with the |
| 156 | +/// system. |
| 157 | +private let __setupStackOnce: Void = { |
| 158 | + let altStackSize = UInt(MINSIGSTKSZ) + (UInt(64) * 1024) |
| 159 | + |
| 160 | + /// Make sure we're not currently executing on an alternate stack already. |
| 161 | + guard sigaltstack(nil, &oldAltStack) == 0 else { return } |
| 162 | + guard oldAltStack.ss_flags & SS_ONSTACK == 0 else { return } |
| 163 | + guard oldAltStack.ss_sp == nil || oldAltStack.ss_size < altStackSize else { |
| 164 | + return |
| 165 | + } |
| 166 | + |
| 167 | + /// Create a new stack struct and save the stack pointer. |
| 168 | + var stack = stack_t() |
| 169 | + stack.ss_size = altStackSize |
| 170 | + stack.ss_sp = malloc(Int(altStackSize)) |
| 171 | + newAltStackPointer = stack.ss_sp |
| 172 | + |
| 173 | + /// Register the system signal routines to use our stack pointer. |
| 174 | + if sigaltstack(&stack, &oldAltStack) != 0 { |
| 175 | + free(stack.ss_sp) |
| 176 | + } |
| 177 | + |
| 178 | + /// Register all known signal handlers. |
| 179 | + for sig in killSigs { |
| 180 | + registerHandler(signal: sig) |
| 181 | + } |
| 182 | +}() |
| 183 | + |
| 184 | +/// Registers an action to the pretty stack trace to be printed if there is a |
| 185 | +/// fatal system error. |
| 186 | +/// |
| 187 | +/// - Parameters: |
| 188 | +/// - action: The action being executed during the stack frame. For example, |
| 189 | +/// "typechecking an AST node". |
| 190 | +/// - actions: A closure containing the actual actions being performed. |
| 191 | +/// - Returns: The result of calling the provided `actions` closure. |
| 192 | +/// - Throws: Whatever is thrown by the `actions` closure. |
| 193 | +public func trace<T>(_ action: String, file: StaticString = #file, |
| 194 | + function: StaticString = #function, line: Int = #line, |
| 195 | + actions: () throws -> T) rethrows -> T { |
| 196 | + _ = __setupStackOnce |
| 197 | + let h = threadLocalHandler() |
| 198 | + h.push(TraceEntry(action: action, function: function, line: line, file: file)) |
| 199 | + defer { h.pop() } |
| 200 | + return try actions() |
| 201 | +} |
| 202 | + |
0 commit comments