Testing fatalError and friends

Answering myself: @TaskLocal

[details=""Dynamic try/catch" implementation with usage example (minimally tested)"]

// ------------------
// MARK: main unchecked exceptions infrastructure

import Foundation

final class DynamicTry {
    private var errors: [Error] = []
    private var tryLevel = 0
    @TaskLocal fileprivate static var shared = DynamicTry()
    
    fileprivate func dynamicTry<T>(_ execute: () -> T, catch: ([Error]) -> Void) -> T {
        tryLevel += 1
        let result = execute()
        tryLevel -= 1
        if !errors.isEmpty {
            let errors = self.errors
            self.errors = []
            `catch`(errors)
        }
        return result
    }
    fileprivate func dynamicThrow(_ error: Error) {
        if tryLevel == 0 {
            fatalError("error caught \(error)")
        }
        self.errors.append(error)
    }
}

func dynamicTry<T>(_ execute: () -> T, catch: ([Error]) -> Void) -> T {
    DynamicTry.shared.dynamicTry(execute, catch: `catch`)
}

func dynamicThrow(_ error: Error) {
    DynamicTry.shared.dynamicThrow(error)
}

typealias SourceLocation = (/*file:*/ String, /*line:*/ Int)
enum DynamicPreconditionError: Error { case preconditionFailed(SourceLocation, String) }

@discardableResult
func dynamicPrecondition(_ expression: Bool, _ message: String? = nil, error: @autoclosure () -> Error? = {nil}(), file: String = #fileID, line: Int = #line) -> Bool {
    guard expression else {
        dynamicThrow(error() ?? DynamicPreconditionError.preconditionFailed((file, line), message ?? "precondition failed"))
        return false
    }
    return true
}

extension [Error] {
    var lines: String {
        var lines = map { String(" - \($0)") }
        lines.insert("errors caught: ", at: 0)
        return lines.joined(separator: "\n")
    }
}

// ------------------
// MARK: precondtion

func preconditionTest() {
    print("preconditionTest started")
    dynamicTry {
        _ = dynamicPrecondition(false)
    } catch: { errors in
        print(errors.lines)
    }
    print("preconditionTest finished")
    
    #if TEST_TRAPS
    dynamicPrecondition(false) // traps
    fatalError("unreachable")
    #endif
}

// ------------------
// MARK: integer overflow

enum Op {
    case add(Int, Int)
    case div(Int, Int)
}
enum IntegerOverflowError: Error { case integerOverflow(SourceLocation, Op) }

extension Int {
    static func + (_ lhs: Self, _ rhs: Self) -> Self {
        lhs.add(rhs)
    }
    static func / (_ lhs: Self, _ rhs: Self) -> Self {
        lhs.div(rhs)
    }
    func add(_ other: Self, file: String = #fileID, line: Int = #line) -> Self {
        let result = addingReportingOverflow(other)
        dynamicPrecondition(!result.overflow, error: IntegerOverflowError.integerOverflow((file, line), .add(self, other)))
        return result.partialValue
    }
    func div(_ other: Self, file: String = #fileID, line: Int = #line) -> Self {
        let result = dividedReportingOverflow(by: other)
        dynamicPrecondition(!result.overflow, error: IntegerOverflowError.integerOverflow((file, line), .div(self, other)))
        return result.partialValue
    }
}

func integerOverflowTest() {
    print("integerOverflowTest started")
    var x: Int = 0
    x = Int.max
    let result = dynamicTry {
        x.add(1)
        // x + 1 as well
    } catch: { errors in
        print(errors.lines)
    }
    print("integerOverflowTest finished, result: \(result)")
    
    #if TEST_TRAPS
    _ = x.add(1) // traps
    fatalError("unreachable")
    #endif
}

// ------------------
// MARK: optional unwrapping

protocol Inittable { init() }

postfix operator ^

enum OptionalUnwrapError: Error { case unwrappingNil(SourceLocation) }

extension Optional where Wrapped: Inittable {
    static postfix func^(v: Self) -> Wrapped {
        v.unwrap()
    }
    func unwrap(file: String = #fileID, line: Int = #line) -> Wrapped {
        switch self {
            case .none:
                dynamicPrecondition(false, error: OptionalUnwrapError.unwrappingNil((file, line)))
                return Wrapped()
            case .some(let wrapped):
                return wrapped
        }
    }
}

extension Int: Inittable {}

func optionalUnwrapTest() {
    print("optionalUnwrapTest started")
    let x: Int? = nil
    let result = dynamicTry {
        x.unwrap()
        // x^ // could be written like this
    } catch: { errors in
        print(errors.lines)
    }
    print("optionalUnwrapTest finished, result: \(result)")
    
    #if TEST_TRAPS
    _ = x.unwrap()
    fatalError("unreachable")
    #endif
}

// ------------------
// MARK: stack overflow

enum StackOverflowError: Error { case stackOverflow(SourceLocation, function: String) }

extension Thread {
    static let minGoodStackSize = 32*1024
    
    var stackSpace: Int {
        var approximateSP = 0
        let thread = pthread_self()
        let stackBase = pthread_get_stackaddr_np(thread)
        let stackSize = pthread_get_stacksize_np(thread)
        let stackLimit = stackBase - stackSize
        guard &approximateSP >= stackLimit && &approximateSP <= stackBase else {
            fatalError("TODO: SOMETHING ODD HERE")
        }
        let remSize = &approximateSP - stackLimit
        return remSize
    }
    func isEnoughStackSpace(for size: Int = minGoodStackSize) -> Bool {
        Thread.stackSpace >= size
    }
    func ensureEnoughStackSpace(for size: Int = minGoodStackSize, file: String = #fileID, line: Int = #line, function: String = #function) -> Bool {
        guard dynamicPrecondition(isEnoughStackSpace(for: size), error: StackOverflowError.stackOverflow((file, line), function: function)) else {
            return false
        }
        return true
    }
    static var stackSpace: Int {
        Thread.current.stackSpace
    }
    static func isEnoughStackSpace(for size: Int = minGoodStackSize) -> Bool {
        Thread.current.isEnoughStackSpace(for: size)
    }
    static func ensureEnoughStackSpace(for size: Int = minGoodStackSize) -> Bool {
        Thread.current.ensureEnoughStackSpace(for: size)
    }
}

@inline(never) func stackAbuser() -> Int {
    guard Thread.ensureEnoughStackSpace() else {
        // dynamicThrow is already thrown inside ensureEnoughStackSpace, no need to do it here
        return 1
    }
    return 1 + stackAbuser()
}

@inline(never) func stackOverflowTest() {
    print("stackOverflowTest started")
    var level = 0
    dynamicTry {
        level = stackAbuser()
    } catch: { errors in
        print("\(errors.lines), level reached: \(level), each frame ~\(Thread.stackSpace / level) bytes")
    }
    print("stackOverflowTest finished, level: \(level)")
}

// ------------------
// MARK: array bounds checks

protocol DynamicThrowable {}
enum IndexOutOfRange: Error { case indexOutOfRange(SourceLocation, Int, Range<Int>) }

extension Array where Element: Inittable {
    private subscript(normal index: Int) -> Element {
        get { self[index] }
        set { self[index] = newValue }
    }
    subscript(at index: Int, file: String = #fileID, line: Int = #line) -> Element {
        get {
            guard dynamicPrecondition(index >= 0 && index < count, error: IndexOutOfRange.indexOutOfRange((file, line), index, 0 ..< count)) else {
                return Element()
            }
            return self[normal: index]
        }
        set {
            guard dynamicPrecondition(index >= 0 && index < count, error: IndexOutOfRange.indexOutOfRange((file, line), index, 0 ..< count)) else {
                return
            }
            self[normal: index] = newValue
        }
    }
}

extension Array where Element: DynamicThrowable & Inittable {
    subscript(_ index: Int) -> Element {
        get { self[at: index] }
        set { self[at: index] = newValue }
    }
}

// add more later:
extension Int: DynamicThrowable {}

func arrayTest() {
    print("arrayTest started")
    var intArray = [1, 2, 3]
    let result = dynamicTry {
        intArray[at: 3]
        // intArray[3] // could be written like this
    } catch: { errors in
        print(errors.lines)
    }
    print("arrayTest finished, result: \(result)")
    
    #if TEST_TRAPS
    _ = intArray[at: 3] // traps
    fatalError("unreachable")
    #endif
}

// ------------------
// MARK: combined test

func combinedTestInternal(_ i: Int = .max) {
    let array = [1, 2, 3]
    let result = 100.div(array[at: i.add(1)])
    // let result = 100 / array[i + 1] // could be written like this
    dynamicPrecondition(result == 50)
}

func combinedTest() {
    print("combinedTest started")
    dynamicTry {
        combinedTestInternal()
    } catch: { errors in
        print(errors.lines)
    }
    print("combinedTest finished")
    
    #if TEST_TRAPS
    combinedTestInternal() // traps
    fatalError("unreachable")
    #endif
}

// ------------------
// MARK: tests
preconditionTest()
integerOverflowTest()
optionalUnwrapTest()
stackOverflowTest()
arrayTest()
combinedTest()

[/details]

Edit: implementation cleaned & fixed.

1 Like