Feel free to use the following "crasher" app to check the various failures @IanPartridge:
I think it covers the majority of situations; Most of them are SIGILL
; In our experiments we also handle SIGABRT
. Very good to know that SIGTRAP
is also used, thanks @Joe_Groff.
import Dispatch
import Foundation
func consumeAny<T>(_ value: T) {
consumeAny(value)
}
func returnTrue() -> Bool {
return true
}
class Foo {
}
func crashIntegerOverflow() {
let x: Int8 = 127
consumeAny(x + (returnTrue() ? 1 : 0))
}
func crashNil() {
let x: Foo? = returnTrue() ? nil : Foo()
consumeAny(x!)
}
func crashFatalError() {
fatalError("deliberately crashing in fatalError")
}
func crashDivBy0() {
consumeAny(1 / (returnTrue() ? 0 : 1))
}
func crashViaCDanglingPointer() {
let x: Int = UnsafeMutableRawPointer(bitPattern: 0x8)!.load(fromByteOffset: 0, as: Int.self)
consumeAny(x + 1)
}
func crashArrayOutOfBounds() {
consumeAny(["nothing"][1])
}
func crashObjCException() {
#if os(macOS)
NSException(name: NSExceptionName("crash"),
reason: "you asked for it",
userInfo: nil).raise()
#endif
fatalError("objc exceptions only supported on macOS")
}
func crashStackOverflow() {
func recurse(accumulator: Int) -> Int {
return 1 + recurse(accumulator: accumulator + 1)
}
consumeAny(recurse(accumulator: 0))
}
func crashOOM() {
#if os(macOS)
var datas: [Data] = []
var i: UInt8 = 1
while true == returnTrue() {
datas.append(Data(repeating: i, count: 1024 * 1024 * 1024))
i += 1
}
consumeAny(datas)
#endif
fatalError("OOM currently only supported on macOS")
}
func crashRangeFromUpperBoundWhichLessThanLowerBound() {
let values = ["one", "two"].suffix(from: 3)
}
struct FooExclusivityViolation {
var x = 0
mutating func addAndCall(_ body: () -> Void) {
self.x += 1
body()
}
}
class BarExclusivityViolation {
var foo = FooExclusivityViolation(x: 0)
func doIt() {
self.foo.addAndCall {
self.foo.addAndCall {}
}
}
}
func crashExclusiveAccessViolation() {
}
let crashTests = [
"integer-overflow": crashIntegerOverflow
, "force-unwrap-nil": crashNil
, "fatal-error": crashFatalError
, "div-by-0": crashDivBy0
, "via-C-dangling-pointer": crashViaCDanglingPointer
, "array-out-of-bounds": crashArrayOutOfBounds
, "objc-exception": crashObjCException
, "stack-overflow": crashStackOverflow
, "out-of-memory": crashOOM
, "range-upperBound-lt-lowerBound": crashRangeFromUpperBoundWhichLessThanLowerBound
, "exclusive-access-violation": crashExclusiveAccessViolation
]
func help() {
let program = CommandLine.arguments[0]
print("Choose one of the following options:")
for key in crashTests.keys {
print(" \(program) \(key)")
}
}
func main() {
let crasher = (crashTests[CommandLine.arguments.suffix(from: 1).first ?? "help"] ?? help)
crasher() // invoke crasher
}
main()
Could you try out your handler with all those, and check if the outputs are all nice? They likely will be, though good to sanity check. If there's more failure situations we should add here let's do so -- maybe you can include this crasher as an example in your library? Want me to PR it there as sample @IanPartridge?
Having that said... As @drexin mentions, these handlers are somewhat "scary" since we don't know if it Swift faulting or some random C code sending those signals. And anything we do in those signal handlers is unsafe. I hope to some day be able to distinguish Swift "soft faults" (e.g. fatalError()
) from memory corrupting "hard faults", but for now let's indeed work with what we have... so, capturing the signals.
On a separate note, @IanPartridge, would you be able to also expose the "print the backtrace nicely" separately as static func
so someone could use it directly, if they handled their own signal handlers (i.e. I don't want to use the install()
call, I have my own way of doing that). I think I'd basically want to call Backtrace.backtrace_full
(using current naming). WDYT?