├── .gitignore ├── .swift-version ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── PrettyStackTrace │ └── PrettyStackTrace.swift └── pst-lite │ └── main.swift └── Tests ├── README.md ├── abort.swift ├── fatal-error.swift ├── long-trace.swift ├── nsexception.swift └── stack-overflow.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /.swiftpm 4 | /Packages 5 | /*.xcodeproj 6 | Package.resolved 7 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - LC_CTYPE=en_US.UTF-8 4 | matrix: 5 | include: 6 | - os: osx 7 | language: objective-c 8 | osx_image: xcode10.2 9 | script: 10 | - swift build 11 | - swift run pst-lite 12 | - os: linux 13 | language: generic 14 | rvm: 15 | - 2.2 16 | - jruby 17 | - 2.0.0-p247 18 | sudo: required 19 | dist: trusty 20 | install: 21 | - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" 22 | script: 23 | - swift build 24 | - swift run pst-lite 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 silt-lang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "PrettyStackTrace", 7 | products: [ 8 | .library( 9 | name: "PrettyStackTrace", 10 | targets: ["PrettyStackTrace"]), 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/apple/swift-package-manager.git", from: "0.1.0"), 14 | .package(url: "https://github.com/llvm-swift/Symbolic.git", from: "0.0.1"), 15 | .package(url: "https://github.com/llvm-swift/FileCheck.git", from: "0.2.0"), 16 | .package(url: "https://github.com/llvm-swift/Lite.git", from: "0.0.3"), 17 | ], 18 | targets: [ 19 | .target(name: "PrettyStackTrace", dependencies: []), 20 | .target(name: "pst-lite", dependencies: ["LiteSupport", "SPMUtility", "Symbolic", "filecheck"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PrettyStackTrace 2 | 3 | PrettyStackTrace allows Swift command-line programs to print a trace of 4 | execution behaviors when a terminating signal is raised, such as a fatal error. 5 | 6 | To use it, wrap your actions in a call to `trace(_:)`. This will 7 | register an entry in the stack trace and (if your process fatal errors) will 8 | print breadcrumbs to stderr describing what was going on when you crashed. 9 | 10 | ## Installation 11 | 12 | PrettyStackTrace is available from the Swift package manager. Add 13 | 14 | ```swift 15 | .package(url: "https://github.com/llvm-swift/PrettyStackTrace.git", from: "0.0.1") 16 | ``` 17 | 18 | to your Package.swift file to use it. 19 | 20 | 21 | ## Example 22 | 23 | ```swift 24 | trace("doing first task") { 25 | print("I'm doing the first task!") 26 | trace("doing second task") { 27 | print("I'm doing the second task!") 28 | fatalError("error on second task!") 29 | } 30 | } 31 | ``` 32 | 33 | This will output: 34 | 35 | ``` 36 | I'm doing the first task! 37 | I'm doing the second task! 38 | Fatal error: error on second task 39 | Stack dump: 40 | -> While doing second task (func main(), in file file.swift, on line 3) 41 | -> While doing first task (func main(), in file file.swift, on line 1) 42 | ``` 43 | 44 | ## Authors 45 | 46 | Harlan Haskins ([@harlanhaskins](https://github.com/harlanhaskins)) 47 | 48 | Robert Widmann ([@CodaFi](https://github.com/CodaFi)) 49 | 50 | ## License 51 | 52 | PrettyStackTrace is released under the MIT license, a copy of which is available 53 | in this repository. 54 | -------------------------------------------------------------------------------- /Sources/PrettyStackTrace/PrettyStackTrace.swift: -------------------------------------------------------------------------------- 1 | /// PrettyStackTrace.swift 2 | /// 3 | /// Copyright 2018-2019, 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 | /// A set of signals that would normally kill the program. We handle these 11 | /// signals by dumping the pretty stack trace description. 12 | let killSigs = [ 13 | SIGILL, SIGABRT, SIGTRAP, SIGFPE, 14 | SIGBUS, SIGSEGV, SIGSYS, SIGQUIT 15 | ] 16 | 17 | /// Needed because the type `sigaction` conflicts with the function `sigaction`. 18 | typealias SigAction = sigaction 19 | 20 | /// A wrapper that associates a signal handler with the number it handles. 21 | struct SigHandler { 22 | /// The signal action, called when the handler is called. 23 | var action: SigAction 24 | 25 | /// The signal number this will fire on. 26 | var signalNumber: Int32 27 | } 28 | 29 | /// Represents an entry in a stack trace. Contains information necessary to 30 | /// reconstruct what was happening at the time this function was executed. 31 | private struct TraceEntry: CustomStringConvertible { 32 | /// A description, in gerund form, of what action was occurring at the time. 33 | /// For example, "typechecking a node". 34 | let action: String 35 | 36 | /// The function (from #function) that was being executed. 37 | let function: StaticString 38 | 39 | /// The line (from #line) that was being executed. 40 | let line: Int 41 | 42 | /// The file (from #file) that was being executed. 43 | let file: StaticString 44 | 45 | /// Constructs a string describing the entry, to be printed in order. 46 | var description: String { 47 | let base = URL(fileURLWithPath: file.description).lastPathComponent 48 | return """ 49 | While \(action) (func \(function), in file \(base), line \(line)) 50 | """ 51 | } 52 | } 53 | 54 | // HACK: This array must be pre-allocated and contains functionally immortal 55 | // C-strings because String may allocate when passed to write(1). 56 | var registeredSignalInfo = 57 | UnsafeMutableBufferPointer(start: 58 | UnsafeMutablePointer.allocate(capacity: killSigs.count), 59 | count: killSigs.count) 60 | var numRegisteredSignalInfo = 0 61 | 62 | /// 8 digits + 2 spaces + 1 period + 1 space + 1 NULL terminator 63 | /// ought to be enough for anyone... 64 | let numberBuffer: UnsafeMutablePointer = { 65 | let count = 13 66 | let ptr = UnsafeMutablePointer.allocate(capacity: count) 67 | 68 | /// NUL terminator 69 | ptr[count - 1] = 0 70 | 71 | return ptr 72 | }() 73 | 74 | /// Fills a pre-allocated 13-byte buffer with the following: 75 | /// 76 | /// [' ', ' ', '1', '2', '3', '4', '5', '6', '7', '8', '.', ' ', '\0'] 77 | /// 78 | /// ensuring that the buffer only uses the exact digits from the integer and 79 | /// doesn't write outside the bounds of the buffer. 80 | /// It then writes the contents of this buffer, starting at the first space at 81 | /// the beginning, to stderr. 82 | private func writeLeadingSpacesAndStackPosition(_ int: UInt) { 83 | // We can't write more than 8 digits. 84 | guard int <= 99_999_999 else { return } 85 | var int = int 86 | 87 | // Fill the buffer from right to left, decrementing the pointer as we add 88 | // characters and digits. 89 | 90 | // First, add the '. ' at the end. 91 | var end = numberBuffer.advanced(by: 12) 92 | end.pointee = 32 /// (ascii ' ') 93 | end = end.predecessor() 94 | end.pointee = 46 /// (ascii '.') 95 | end = end.predecessor() 96 | 97 | // Next, pop successive digits off the end of the integer and add them to 98 | // the current 'start' of the buffer. 99 | while int > 0 { 100 | let remInt = int.remainderReportingOverflow(dividingBy: 10).partialValue 101 | let remInt8 = Int8(truncatingIfNeeded: remInt) 102 | int = int.dividedReportingOverflow(by: 10).0 103 | end.pointee = remInt8 &+ 48 /// (ascii '0') 104 | end = end.predecessor() 105 | } 106 | 107 | // Add the ' ' at the current 'start' of the buffer. 108 | end.pointee = 32 /// (ascii ' ') 109 | end = end.predecessor() 110 | end.pointee = 32 /// (ascii ' ') 111 | // Don't move to the predecessor -- we're at the beginning of the string now. 112 | 113 | // Find the distance between the end of the buffer and the beginning of our 114 | // string. 115 | let dist = abs(numberBuffer.advanced(by: 13).distance(to: end)) 116 | write(STDERR_FILENO, end, dist) 117 | } 118 | 119 | /// A class managing a stack of trace entries. When a particular thread gets 120 | /// a kill signal, this handler will dump all the entries in the tack trace and 121 | /// end the process. 122 | private class PrettyStackTraceManager { 123 | struct StackEntry { 124 | var prev: UnsafeMutablePointer? 125 | let data: UnsafeMutablePointer 126 | let count: Int 127 | } 128 | 129 | /// Keeps a stack of serialized trace entries in reverse order. 130 | /// - Note: This keeps strings, because it's not safe to 131 | /// construct the strings in the signal handler directly. 132 | var stack: UnsafeMutablePointer? = nil 133 | 134 | private let stackDumpMsg: StackEntry 135 | init() { 136 | let msg = "Stack dump:\n" 137 | stackDumpMsg = StackEntry(prev: nil, 138 | data: strndup(msg, msg.utf8.count), 139 | count: msg.utf8.count) 140 | } 141 | 142 | /// Pushes the description of a trace entry to the stack. 143 | func push(_ entry: TraceEntry) { 144 | let str = "\(entry.description)\n" 145 | let newEntry = StackEntry(prev: stack, 146 | data: strndup(str, str.count), 147 | count: str.count) 148 | let newStack = UnsafeMutablePointer.allocate(capacity: 1) 149 | newStack.pointee = newEntry 150 | stack = newStack 151 | } 152 | 153 | /// Pops the latest trace entry off the stack. 154 | func pop() { 155 | guard let stack = stack else { return } 156 | let prev = stack.pointee.prev 157 | free(stack.pointee.data) 158 | stack.deallocate() 159 | self.stack = prev 160 | } 161 | 162 | /// Dumps the stack entries to standard error, starting with the most 163 | /// recent entry. 164 | func dump(_ signal: Int32) { 165 | write(STDERR_FILENO, stackDumpMsg.data, stackDumpMsg.count) 166 | var i: UInt = 1 167 | var cur = stack 168 | while cur != nil { 169 | writeLeadingSpacesAndStackPosition(i) 170 | let entry = cur.unsafelyUnwrapped 171 | write(STDERR_FILENO, entry.pointee.data, entry.pointee.count) 172 | cur = entry.pointee.prev 173 | i += 1 174 | } 175 | } 176 | } 177 | 178 | /// Storage for a thread-local context key to get the thread local trace 179 | /// handler. 180 | private var __stackContextKey = pthread_key_t() 181 | 182 | /// Creates a key for a thread-local reference to a PrettyStackTraceHandler. 183 | private var stackContextKey: pthread_key_t = { 184 | pthread_key_create(&__stackContextKey) { ptr in 185 | #if os(Linux) 186 | guard let ptr = ptr else { 187 | return 188 | } 189 | #endif 190 | let unmanaged = Unmanaged.fromOpaque(ptr) 191 | unmanaged.release() 192 | } 193 | return __stackContextKey 194 | }() 195 | 196 | /// A thread-local reference to a PrettyStackTraceManager. 197 | /// The first time this is created, this will create the handler and register 198 | /// it with `pthread_setspecific`. 199 | private func threadLocalHandler() -> PrettyStackTraceManager { 200 | guard let specificPtr = pthread_getspecific(stackContextKey) else { 201 | let handler = PrettyStackTraceManager() 202 | let unmanaged = Unmanaged.passRetained(handler) 203 | pthread_setspecific(stackContextKey, unmanaged.toOpaque()) 204 | return handler 205 | } 206 | let unmanaged = Unmanaged.fromOpaque(specificPtr) 207 | return unmanaged.takeUnretainedValue() 208 | } 209 | 210 | extension Int32 { 211 | /// HACK: Just for compatibility's sake on Linux. 212 | public init(bitPattern: Int32) { self = bitPattern } 213 | } 214 | 215 | /// Registers the pretty stack trace signal handlers. 216 | private func registerHandler(signal: Int32) { 217 | var newHandler = SigAction() 218 | let cHandler: @convention(c) (Int32) -> Swift.Void = { signalNumber in 219 | unregisterHandlers() 220 | 221 | // Unblock all potentially blocked kill signals 222 | var sigMask = sigset_t() 223 | sigfillset(&sigMask) 224 | sigprocmask(SIG_UNBLOCK, &sigMask, nil) 225 | 226 | threadLocalHandler().dump(signalNumber) 227 | exit(signalNumber) 228 | } 229 | #if os(macOS) 230 | newHandler.__sigaction_u.__sa_handler = cHandler 231 | #elseif os(Linux) 232 | newHandler.__sigaction_handler = .init(sa_handler: cHandler) 233 | #else 234 | fatalError("Cannot register signal action handler on this platform") 235 | #endif 236 | newHandler.sa_flags = Int32(bitPattern: SA_NODEFER) | 237 | Int32(bitPattern: SA_RESETHAND) | 238 | Int32(bitPattern: SA_ONSTACK) 239 | sigemptyset(&newHandler.sa_mask) 240 | 241 | var handler = SigAction() 242 | if sigaction(signal, &newHandler, &handler) != 0 { 243 | let sh = SigHandler(action: handler, signalNumber: signal) 244 | registeredSignalInfo[numRegisteredSignalInfo] = sh 245 | numRegisteredSignalInfo += 1 246 | } 247 | } 248 | 249 | /// Unregisters all pretty stack trace signal handlers. 250 | private func unregisterHandlers() { 251 | var i = 0 252 | while i < killSigs.count { 253 | sigaction(registeredSignalInfo[i].signalNumber, 254 | ®isteredSignalInfo[i].action, nil) 255 | i += 1 256 | } 257 | 258 | // HACK: Must leak the old registerdSignalInfo because we cannot safely 259 | // free inside a signal handler. 260 | // cannot: free(registeredSignalInfo) 261 | numRegisteredSignalInfo = 0 262 | } 263 | 264 | /// A reference to the previous alternate stack, if any. 265 | private var oldAltStack = stack_t() 266 | 267 | /// The current stack pointer for this alternate stack. 268 | private var newAltStackPointer: UnsafeMutableRawPointer? 269 | 270 | /// Sets up an alternate stack and registers all signal handlers with the 271 | /// system. 272 | private let __setupStackOnce: Void = { 273 | #if os(macOS) 274 | typealias SSSize = UInt 275 | #else 276 | typealias SSSize = Int 277 | #endif 278 | 279 | _ = numberBuffer 280 | 281 | let altStackSize = SSSize(MINSIGSTKSZ) + (SSSize(64) * 1024) 282 | 283 | /// Make sure we're not currently executing on an alternate stack already. 284 | guard sigaltstack(nil, &oldAltStack) == 0 else { return } 285 | guard Int(oldAltStack.ss_flags) & Int(SS_ONSTACK) == 0 else { return } 286 | guard oldAltStack.ss_sp == nil || oldAltStack.ss_size < altStackSize else { 287 | return 288 | } 289 | 290 | /// Create a new stack struct and save the stack pointer. 291 | var stack = stack_t() 292 | stack.ss_size = altStackSize 293 | stack.ss_sp = malloc(Int(altStackSize)) 294 | newAltStackPointer = stack.ss_sp 295 | 296 | /// Register the system signal routines to use our stack pointer. 297 | if sigaltstack(&stack, &oldAltStack) != 0 { 298 | free(stack.ss_sp) 299 | } 300 | 301 | /// Register all known signal handlers. 302 | for sig in killSigs { 303 | registerHandler(signal: sig) 304 | } 305 | }() 306 | 307 | /// Registers an action to the pretty stack trace to be printed if there is a 308 | /// fatal system error. 309 | /// 310 | /// - Parameters: 311 | /// - action: The action being executed during the stack frame. For example, 312 | /// "typechecking an AST node". 313 | /// - actions: A closure containing the actual actions being performed. 314 | /// - Returns: The result of calling the provided `actions` closure. 315 | /// - Throws: Whatever is thrown by the `actions` closure. 316 | public func trace(_ action: String, file: StaticString = #file, 317 | function: StaticString = #function, line: Int = #line, 318 | actions: () throws -> T) rethrows -> T { 319 | _ = __setupStackOnce 320 | let h = threadLocalHandler() 321 | h.push(TraceEntry(action: action, function: function, line: line, file: file)) 322 | defer { h.pop() } 323 | return try actions() 324 | } 325 | -------------------------------------------------------------------------------- /Sources/pst-lite/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import Glibc 4 | #endif 5 | import LiteSupport 6 | import Symbolic 7 | 8 | /// Finds the named executable relative to the location of the `lite` 9 | /// executable. 10 | func findAdjacentBinary(_ name: String) -> URL? { 11 | guard let path = SymbolInfo(address: #dsohandle)?.filename else { return nil } 12 | let siltURL = path.deletingLastPathComponent() 13 | .appendingPathComponent(name) 14 | guard FileManager.default.fileExists(atPath: siltURL.path) else { return nil } 15 | return siltURL 16 | } 17 | 18 | /// Runs `lite` looking for `.test` files and executing them. 19 | do { 20 | let fileCheck = findAdjacentBinary("filecheck")! 21 | 22 | let subs = [ 23 | ("FileCheck", fileCheck.path) 24 | ] 25 | let allPassed = 26 | try runLite(substitutions: subs, 27 | pathExtensions: ["swift"], 28 | testDirPath: nil, 29 | testLinePrefix: "//", 30 | parallelismLevel: .automatic) 31 | exit(allPassed ? 0 : -1) 32 | } catch let err as LiteError { 33 | fputs("error: \(err.message)", stderr) 34 | exit(-1) 35 | } catch { 36 | #if os(macOS) 37 | fatalError("unhandled error: \(error)") 38 | #endif 39 | } 40 | -------------------------------------------------------------------------------- /Tests/README.md: -------------------------------------------------------------------------------- 1 | # Note for these tests! 2 | 3 | These tests all use `CHECK-DAG` in their FileCheck tests because input 4 | redirection using `2>&1` leads to non-deterministically ordered input when piped. 5 | -------------------------------------------------------------------------------- /Tests/abort.swift: -------------------------------------------------------------------------------- 1 | // RUN: cat %S/../Sources/PrettyStackTrace/PrettyStackTrace.swift %s | swiftc -c -emit-executable -o %t - && %t 2>&1 | %FileCheck %s 2 | 3 | #if os(macOS) 4 | import Darwin 5 | #elseif os(Linux) 6 | import Glibc 7 | #endif 8 | 9 | // CHECK-DAG: in first task! 10 | // CHECK-DAG: in second task! 11 | // CHECK-DAG: Stack dump 12 | // CHECK-DAG: 1. While doing first task 13 | trace("doing first task") { 14 | print("in first task!") 15 | trace("doing second task") { 16 | print("in second task!") 17 | } 18 | abort() 19 | } 20 | -------------------------------------------------------------------------------- /Tests/fatal-error.swift: -------------------------------------------------------------------------------- 1 | // RUN: cat %S/../Sources/PrettyStackTrace/PrettyStackTrace.swift %s | swiftc -c -emit-executable -o %t - && %t 2>&1 | %FileCheck %s 2 | 3 | // CHECK-DAG: in first task! 4 | // CHECK-DAG: in second task! 5 | // CHECK-DAG: {{[fF]}}atal error: second task failed 6 | // CHECK-DAG: Stack dump 7 | // CHECK-DAG: 1. While doing second task 8 | // CHECK-DAG: 2. While doing first task 9 | trace("doing first task") { 10 | print("in first task!") 11 | trace("doing second task") { 12 | print("in second task!") 13 | fatalError("second task failed") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/long-trace.swift: -------------------------------------------------------------------------------- 1 | // RUN: cat %S/../Sources/PrettyStackTrace/PrettyStackTrace.swift %s | swiftc -c -emit-executable -o %t - && %t 2>&1 | %FileCheck %s 2 | 3 | // CHECK-DAG: Stack dump 4 | // CHECK-DAG: 1. While in frame 100 5 | // CHECK-DAG: 2. While in frame 99 6 | // CHECK-DAG: 3. While in frame 98 7 | // CHECK-DAG: 4. While in frame 97 8 | // CHECK-DAG: 5. While in frame 96 9 | // CHECK-DAG: 6. While in frame 95 10 | // CHECK-DAG: 7. While in frame 94 11 | // CHECK-DAG: 8. While in frame 93 12 | // CHECK-DAG: 9. While in frame 92 13 | // CHECK-DAG: 10. While in frame 91 14 | // CHECK-DAG: 11. While in frame 90 15 | // CHECK-DAG: 12. While in frame 89 16 | // CHECK-DAG: 13. While in frame 88 17 | // CHECK-DAG: 14. While in frame 87 18 | // CHECK-DAG: 15. While in frame 86 19 | // CHECK-DAG: 16. While in frame 85 20 | // CHECK-DAG: 17. While in frame 84 21 | // CHECK-DAG: 18. While in frame 83 22 | // CHECK-DAG: 19. While in frame 82 23 | // CHECK-DAG: 20. While in frame 81 24 | // CHECK-DAG: 21. While in frame 80 25 | // CHECK-DAG: 22. While in frame 79 26 | // CHECK-DAG: 23. While in frame 78 27 | // CHECK-DAG: 24. While in frame 77 28 | // CHECK-DAG: 25. While in frame 76 29 | // CHECK-DAG: 26. While in frame 75 30 | // CHECK-DAG: 27. While in frame 74 31 | // CHECK-DAG: 28. While in frame 73 32 | // CHECK-DAG: 29. While in frame 72 33 | // CHECK-DAG: 30. While in frame 71 34 | // CHECK-DAG: 31. While in frame 70 35 | // CHECK-DAG: 32. While in frame 69 36 | // CHECK-DAG: 33. While in frame 68 37 | // CHECK-DAG: 34. While in frame 67 38 | // CHECK-DAG: 35. While in frame 66 39 | // CHECK-DAG: 36. While in frame 65 40 | // CHECK-DAG: 37. While in frame 64 41 | // CHECK-DAG: 38. While in frame 63 42 | // CHECK-DAG: 39. While in frame 62 43 | // CHECK-DAG: 40. While in frame 61 44 | // CHECK-DAG: 41. While in frame 60 45 | // CHECK-DAG: 42. While in frame 59 46 | // CHECK-DAG: 43. While in frame 58 47 | // CHECK-DAG: 44. While in frame 57 48 | // CHECK-DAG: 45. While in frame 56 49 | // CHECK-DAG: 46. While in frame 55 50 | // CHECK-DAG: 47. While in frame 54 51 | // CHECK-DAG: 48. While in frame 53 52 | // CHECK-DAG: 49. While in frame 52 53 | // CHECK-DAG: 50. While in frame 51 54 | // CHECK-DAG: 51. While in frame 50 55 | // CHECK-DAG: 52. While in frame 49 56 | // CHECK-DAG: 53. While in frame 48 57 | // CHECK-DAG: 54. While in frame 47 58 | // CHECK-DAG: 55. While in frame 46 59 | // CHECK-DAG: 56. While in frame 45 60 | // CHECK-DAG: 57. While in frame 44 61 | // CHECK-DAG: 58. While in frame 43 62 | // CHECK-DAG: 59. While in frame 42 63 | // CHECK-DAG: 60. While in frame 41 64 | // CHECK-DAG: 61. While in frame 40 65 | // CHECK-DAG: 62. While in frame 39 66 | // CHECK-DAG: 63. While in frame 38 67 | // CHECK-DAG: 64. While in frame 37 68 | // CHECK-DAG: 65. While in frame 36 69 | // CHECK-DAG: 66. While in frame 35 70 | // CHECK-DAG: 67. While in frame 34 71 | // CHECK-DAG: 68. While in frame 33 72 | // CHECK-DAG: 69. While in frame 32 73 | // CHECK-DAG: 70. While in frame 31 74 | // CHECK-DAG: 71. While in frame 30 75 | // CHECK-DAG: 72. While in frame 29 76 | // CHECK-DAG: 73. While in frame 28 77 | // CHECK-DAG: 74. While in frame 27 78 | // CHECK-DAG: 75. While in frame 26 79 | // CHECK-DAG: 76. While in frame 25 80 | // CHECK-DAG: 77. While in frame 24 81 | // CHECK-DAG: 78. While in frame 23 82 | // CHECK-DAG: 79. While in frame 22 83 | // CHECK-DAG: 80. While in frame 21 84 | // CHECK-DAG: 81. While in frame 20 85 | // CHECK-DAG: 82. While in frame 19 86 | // CHECK-DAG: 83. While in frame 18 87 | // CHECK-DAG: 84. While in frame 17 88 | // CHECK-DAG: 85. While in frame 16 89 | // CHECK-DAG: 86. While in frame 15 90 | // CHECK-DAG: 87. While in frame 14 91 | // CHECK-DAG: 88. While in frame 13 92 | // CHECK-DAG: 89. While in frame 12 93 | // CHECK-DAG: 90. While in frame 11 94 | // CHECK-DAG: 91. While in frame 10 95 | // CHECK-DAG: 92. While in frame 9 96 | // CHECK-DAG: 93. While in frame 8 97 | // CHECK-DAG: 94. While in frame 7 98 | // CHECK-DAG: 95. While in frame 6 99 | // CHECK-DAG: 96. While in frame 5 100 | // CHECK-DAG: 97. While in frame 4 101 | // CHECK-DAG: 98. While in frame 3 102 | // CHECK-DAG: 99. While in frame 2 103 | // CHECK-DAG: 100. While in frame 1 104 | func buildTrace(_ int: Int = 1) { 105 | if int > 100 { abort() } 106 | trace("in frame \(int)") { 107 | buildTrace(int + 1) 108 | } 109 | } 110 | 111 | buildTrace() 112 | -------------------------------------------------------------------------------- /Tests/nsexception.swift: -------------------------------------------------------------------------------- 1 | // RUN: cat %S/../Sources/PrettyStackTrace/PrettyStackTrace.swift %s | swiftc -c -emit-executable -o %t - && %t 2>&1 | %FileCheck %s 2 | 3 | import Foundation 4 | 5 | // CHECK-DAG: in first task! 6 | // CHECK-DAG: about to raise! 7 | // CHECK-DAG: Terminating app due to uncaught exception 'NSGenericException', reason: 'You failed' 8 | // CHECK-DAG: Stack dump: 9 | // CHECK-DAG: 1. While raising an exception 10 | // CHECK-DAG: 2. While doing first task 11 | trace("doing first task") { 12 | print("in first task!") 13 | trace("raising an exception") { 14 | print("about to raise!") 15 | #if os(macOS) 16 | let exception = NSException(name: .genericException, reason: "You failed") 17 | exception.raise() 18 | #else 19 | print("Terminating app due to uncaught exception 'NSGenericException', reason: 'You failed'") 20 | raise(SIGILL) 21 | #endif 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/stack-overflow.swift: -------------------------------------------------------------------------------- 1 | // RUN: cat %S/../Sources/PrettyStackTrace/PrettyStackTrace.swift %s | swiftc -c -emit-executable -o %t - && %t 2>&1 | %FileCheck %s 2 | 3 | func overflow() { 4 | print("", terminator: "") 5 | overflow() 6 | } 7 | 8 | // CHECK-DAG: in first task! 9 | // CHECK-DAG: in second task! 10 | // CHECK-DAG: Stack dump 11 | // CHECK-DAG: 1. While doing second task 12 | // CHECK-DAG: 2. While doing first task 13 | trace("doing first task") { 14 | print("in first task!") 15 | trace("doing second task") { 16 | print("in second task!") 17 | overflow() 18 | } 19 | } 20 | --------------------------------------------------------------------------------