Skip to content

Commit 32fee9f

Browse files
committed
Initial commit
0 parents  commit 32fee9f

File tree

14 files changed

+568
-0
lines changed

14 files changed

+568
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
Package.resolved

.swift-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
4.0

.travis.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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: xcode9
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

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.

Package.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// swift-tools-version:4.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.0.5"),
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", "Utility", "Symbolic"]),
21+
.target(
22+
name: "pst-file-check",
23+
dependencies: ["FileCheck", "Utility"]),
24+
25+
]
26+
)

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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

Comments
 (0)