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.